summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-01-11 18:58:50 +0000
committerChris McDonough <chrism@agendaless.com>2010-01-11 18:58:50 +0000
commit9ec2d646eb23b88e4ef8119b0b46240081953daf (patch)
tree80296b92edb8310bfb7699d165e92a1ea9e36256
parent96d63068ba033809ed73834e095f274800e4d4e8 (diff)
downloadpyramid-9ec2d646eb23b88e4ef8119b0b46240081953daf.tar.gz
pyramid-9ec2d646eb23b88e4ef8119b0b46240081953daf.tar.bz2
pyramid-9ec2d646eb23b88e4ef8119b0b46240081953daf.zip
Merge of andrew-docs branch.
-rw-r--r--TODO.txt24
-rw-r--r--docs/authorintro.rst64
-rwxr-xr-xdocs/build_book4
-rw-r--r--docs/conf.py101
-rw-r--r--docs/copyright.rst39
-rw-r--r--docs/glossary.rst15
-rw-r--r--docs/index.rst2
-rw-r--r--docs/latexindex.rst5
-rw-r--r--docs/narr/configuration.rst949
-rw-r--r--docs/narr/environment.rst12
-rw-r--r--docs/narr/events.rst92
-rw-r--r--docs/narr/extending.rst66
-rw-r--r--docs/narr/firstapp.rst767
-rw-r--r--docs/narr/hybrid.rst205
-rw-r--r--docs/narr/install.rst107
-rw-r--r--docs/narr/introduction.rst257
-rw-r--r--docs/narr/models.rst107
-rw-r--r--docs/narr/project.rst386
-rw-r--r--docs/narr/router.rst86
-rw-r--r--docs/narr/scanning.rst202
-rw-r--r--docs/narr/security.rst219
-rw-r--r--docs/narr/startup.rst37
-rw-r--r--docs/narr/templates.rst440
-rw-r--r--docs/narr/unittesting.rst44
-rw-r--r--docs/narr/urldispatch.rst60
-rw-r--r--docs/narr/urlmapping.rst43
-rw-r--r--docs/narr/vhosting.rst50
-rw-r--r--docs/narr/views.rst353
-rw-r--r--docs/narr/webob.rst2
-rw-r--r--docs/thanks.rst17
-rw-r--r--docs/tutorials/bfgwiki/authorization.rst3
-rw-r--r--docs/tutorials/bfgwiki/basiclayout.rst3
-rw-r--r--docs/tutorials/bfgwiki/definingmodels.rst3
-rw-r--r--docs/tutorials/bfgwiki/definingviews.rst12
-rw-r--r--docs/tutorials/bfgwiki/index.rst4
-rw-r--r--docs/tutorials/bfgwiki2/authorization.rst4
-rw-r--r--docs/tutorials/bfgwiki2/basiclayout.rst3
-rw-r--r--docs/tutorials/bfgwiki2/definingmodels.rst3
-rw-r--r--docs/tutorials/bfgwiki2/definingviews.rst12
-rw-r--r--docs/tutorials/bfgwiki2/index.rst4
-rw-r--r--docs/whatsnew-1.2.rst4
-rw-r--r--repoze/bfg/events.py12
42 files changed, 2720 insertions, 2102 deletions
diff --git a/TODO.txt b/TODO.txt
index 290c91822..2bdc169e9 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,10 +1,24 @@
-- Audit the rest of the docs for "view configuration" and "route
- configuration" references, etc.
-
-- Basic WSGI documentation (pipeline / app / server).
-
- Decide on ``unhook_zca`` argument to ``tearDown``.
- Get rid of ITraverser hook in favor of IViewLookup.
- Named notfound views.
+
+- Review:
+
+ [ ] Traversal
+
+ [ ] URL dispatch
+
+ [ ] Combining Traversal and URL Dispatch
+
+ [ ] Views
+
+- Review tutorials.
+
+- Review index.
+
+- Review API docs.
+
+- Basic WSGI documentation (pipeline / app / server).
+
diff --git a/docs/authorintro.rst b/docs/authorintro.rst
new file mode 100644
index 000000000..1a0130a11
--- /dev/null
+++ b/docs/authorintro.rst
@@ -0,0 +1,64 @@
+Author Introduction
+===================
+
+This is the "author introduction", wherein, I, the author put stuff
+that goes nowhere else.
+
+The Genesis of :mod:`repoze.bfg`
+--------------------------------
+
+I wrote :mod:`repoze.bfg` after many years of writing applications
+using :term:`Zope`. Zope provided me with a lot of mileage: it wasn't
+until almost a decade of succesfully creating applications using Zope
+that I decided to write a different web framework. Although
+:mod:`repoze.bfg` takes inspiration from a variety of web frameworks,
+it owes more of its design ethos to Zope than any other.
+
+The "Repoze" brand existed before :mod:`repoze.bfg`. One of the first
+packages developed as part of the Repoze brand was a package named
+:mod:`repoze.zope2`. This was a package that allowed Zope 2
+applications to run under a :term:`WSGI` server without modification.
+Zope 2 did not have reasonable WSGI support at the time.
+
+During the development of the :mod:`repoze.zope2` package, I found
+that replicating the Zope 2 "publisher" -- the machinery that maps
+URLs to code -- was time-consuming and fiddly. Zope 2 had evolved
+over many years, and emulating all of its edge cases was extremely
+difficult. I finished the :mod:`repoze.zope2` package, and it
+emulates the normal Zope 2 publisher pretty well. But during its
+development, it became clear that Zope 2 had simply begun to exceed my
+tolerance for complexity, and I began to look around for simpler
+options.
+
+I considered the using Zope 3 application server machinery, but it
+turned out that it had become more indirect than the Zope 2 machinery
+it aimed to replace, which didn't fulfill the goal of simplification.
+I also considered using Django and Pylons, but neither of those
+frameworks offer much along the axes of traversal, contextual
+declarative security, or application extensibility; these were
+features I had become accustomed to as a Zope developer.
+
+I decided that in the long term, creating a simpler, legacy-free
+framework that retained features I had become accustomed to when
+developing Zope applications was a more reasonable idea than
+continuing to use any Zope publisher or living with the limitations
+and unfamiliarities of a different framework. The result is what is
+now :mod:`repoze.bfg`.
+
+Thanks
+------
+
+This book is dedicated to my grandmother, Dorothy Phillips.
+
+Thanks to the following people for providing expertise, resources, and
+software. Without the help of these folks, neither this book nor the
+software which it details would exist: Paul Everitt, Tres Seaver,
+Andrew Sawyers, Malthe Borch, Carlos de la Guardia, Georg Brandl,
+Simon Oram of Electrosoup, Ian Bicking of the Open Planning Project,
+Jim Fulton of Zope Corporation, Tom Moroz of the Open Society
+Institute, and Todd Koym of Environmental Health Sciences.
+
+Special thanks to Guido van Rossum and Tim Peters for Python.
+
+Special thanks also to Tricia for putting up with me.
+
diff --git a/docs/build_book b/docs/build_book
new file mode 100755
index 000000000..771da7dd3
--- /dev/null
+++ b/docs/build_book
@@ -0,0 +1,4 @@
+#!/bin/sh
+BOOK=1 make clean latex
+cd .build/latex
+make all
diff --git a/docs/conf.py b/docs/conf.py
index bb78d758d..64182333e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -211,24 +211,29 @@ _PREAMBLE = r"""
\pagestyle{fancy}
+% header and footer styles
\renewcommand{\chaptermark}[1]%
-{\markboth{\MakeUppercase{\thechapter.\ #1}}{}}
+ {\markboth{\MakeUppercase{\thechapter.\ #1}}{}
+ }
\renewcommand{\sectionmark}[1]%
-{\markright{\MakeUppercase{\thesection.\ #1}}}
-\renewcommand{\headrulewidth}{0.5pt}
+ {\markright{\MakeUppercase{\thesection.\ #1}}
+ }
+
+% defaults for fancy style
+\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\fancyhf{}
\fancyfoot[C]{\thepage}
-\fancyhead[RO]{\rightmark}
-\fancyhead[LE]{\leftmark}
+% plain style
\fancypagestyle{plain}{
- \fancyhf{} % empty header and footer
\renewcommand{\headrulewidth}{0pt} % ho header line
- \renewcommand{\footrulewidth}{0pt}% not footer line
- \fancyfoot[C]{\thepage}% like fancy style
+ \renewcommand{\footrulewidth}{0pt}% no footer line
+ \fancyhf{} % empty header and footer
+ \fancyfoot[C]{\thepage}
}
+% title page styles
\makeatletter
\def\@subtitle{\relax}
\newcommand{\subtitle}[1]{\gdef\@subtitle{#1}}
@@ -242,7 +247,7 @@ _PREAMBLE = r"""
}
\makeatother
-% Redefine these colors to your liking in the preamble.
+% Redefine link and title colors
\definecolor{TitleColor}{rgb}{0,0,0}
\definecolor{InnerLinkColor}{rgb}{0.208,0.374,0.486}
\definecolor{OuterLinkColor}{rgb}{0.216,0.439,0.388}
@@ -252,29 +257,13 @@ _PREAMBLE = r"""
\definecolor{VerbatimBorderColor}{rgb}{1,1,1}
\makeatletter
-% Notices / Admonitions
-%
-\newcommand{\py@veryheavybox}{
- \setlength{\fboxrule}{2pt}
- \setlength{\fboxsep}{7pt}
- \setlength{\py@noticelength}{\linewidth}
- \addtolength{\py@noticelength}{-2\fboxsep}
- \addtolength{\py@noticelength}{-2\fboxrule}
- \setlength{\shadowsize}{3pt}
- \Sbox
- \minipage{\py@noticelength}
-}
-\newcommand{\py@endveryheavybox}{
- \endminipage
- \endSbox
- \fbox{\TheSbox}
-}
\renewcommand{\py@noticestart@warning}{\py@heavybox}
\renewcommand{\py@noticeend@warning}{\py@endheavybox}
\renewcommand{\py@noticestart@note}{\py@heavybox}
\renewcommand{\py@noticeend@note}{\py@endheavybox}
\makeatother
+% icons in note and warning boxes
\usepackage{ifthen}
% Keep a copy of the original notice environment
\let\origbeginnotice\notice
@@ -291,7 +280,20 @@ _PREAMBLE = r"""
\origendnotice% equivalent to original \end{notice}
}
+% try to prevent code-block boxes from splitting across pages
\sloppy
+\widowpenalty=300
+\clubpenalty=300
+\setlength{\parskip}{3ex plus 2ex minus 2ex}
+
+% suppress page numbers on pages showing part title
+\makeatletter
+\let\sv@endpart\@endpart
+\def\@endpart{\thispagestyle{empty}\sv@endpart}
+\makeatother
+
+% prevent page numbers in TOC (reset to fancy by frontmatter directive)
+\pagestyle{empty}
"""
latex_elements = {
@@ -305,17 +307,58 @@ latex_elements = {
from docutils import nodes
+# secnumdepth counter reset to 2 causes numbering in related matter;
+# reset to -1 causes chapters to not be numbered, reset to -2 causes
+# parts to not be numbered.
+
+#part -1
+#chapter 0
+#section 1
+#subsection 2
+#subsubsection 3
+#paragraph 4
+#subparagraph 5
+
def frontmatter(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
- return [nodes.raw('', '\\frontmatter\n', format='latex')]
+ return [nodes.raw(
+ '',
+ r"""
+\frontmatter
+% prevent part/chapter/section numbering
+\setcounter{secnumdepth}{-2}
+% suppress headers
+\pagestyle{plain}
+% reset page counter
+\setcounter{page}{1}
+% suppress first toc pagenum
+\addtocontents{toc}{\protect\thispagestyle{empty}}
+""",
+ format='latex')]
def mainmatter(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
- return [nodes.raw('', '\\mainmatter\n', format='latex')]
+ return [nodes.raw(
+ '',
+ r"""
+\mainmatter
+% allow part/chapter/section numbering
+\setcounter{secnumdepth}{2}
+% get headers back
+\pagestyle{fancy}
+\fancyhf{}
+\renewcommand{\headrulewidth}{0.5pt}
+\renewcommand{\footrulewidth}{0pt}
+\fancyfoot[C]{\thepage}
+\fancyhead[RO]{\rightmark}
+\fancyhead[LE]{\leftmark}
+""",
+ format='latex')]
def backmatter(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
- return [nodes.raw('', '\\backmatter\n', format='latex')]
+ return [nodes.raw('', '\\backmatter\n\\setcounter{secnumdepth}{-1}\n',
+ format='latex')]
def setup(app):
app.add_directive('frontmatter', frontmatter, 1, (0, 0, 0))
diff --git a/docs/copyright.rst b/docs/copyright.rst
index a99382c8f..59fe2e013 100644
--- a/docs/copyright.rst
+++ b/docs/copyright.rst
@@ -1,5 +1,5 @@
-Copyright and Trademarks
-========================
+Copyright, Trademarks, and Attributions
+=======================================
*The repoze.bfg Web Application Framework*
@@ -11,7 +11,7 @@ Copyright |copy| 2008-2010, Agendaless Consulting.
ISBN-13: 978-0-615-34537-6
-First publishing: 2010
+First print publishing: 2010
All rights reserved. This material may be copied or distributed only
subject to the terms and conditions set forth in the `Creative Commons
@@ -30,14 +30,24 @@ similar license to this one.
`less restrictive (BSD-like) license
<http://repoze.org/license.html>`_ .
-**Trademarks**
-
All terms mentioned in this book that are known to be trademarks or
service marks have been appropriately capitalized. However, use of a
term in this book should not be regarded as affecting the validity of
any trademark or service mark.
-**Attributions**
+Every effort has been made to make this book as complete and as
+accurate as possible, but no warranty or fitness is implied. The
+information provided is on as "as-is" basis. The author and the
+publisher shall have neither liability nor responsibility to any
+person or entity with respect to any loss or damages arising from the
+information contained in this book. No patent liability is assumed
+with respect to the use of the information contained herein.
+
+Attributions
+------------
+
+Technical Reviewer:
+ Andrew Sawyers
Cover Designer:
`Electrosoup <http://www.electrosoup.co.uk>`_.
@@ -46,17 +56,11 @@ Used with permission:
The :ref:`webob_chapter` chapter is adapted, with permission, from
documentation originally written by Ian Bicking.
-**Warning and Disclaimer**
-
-Every effort has been made to make this book as complete and as
-accurate as possible, but no warranty or fitness is implied. The
-information provided is on as "as-is" basis. The author and the
-publisher shall have neither liability nor responsibility to any
-person or entity with respect to any loss or damages arising from the
-information contained in this book. No patent liability is assumed
-with respect to the use of the information contained herein.
+Foreword:
+ Paul Everitt
-**Contacting The Publisher**
+Contacting The Publisher
+------------------------
Please send documentation licensing inquiries, translation inquiries,
and other business communications to `Agendaless Consulting
@@ -64,7 +68,8 @@ and other business communications to `Agendaless Consulting
technical queries to the `repoze-dev maillist
<http://lists.repoze.org/listinfo/repoze-dev>`_.
-**Source Code**
+Source Code
+-----------
The source code for this book is available within the
:mod:`repoze.bfg` software distribution, always available via
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 1e462db8e..ef1ad8144 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -621,3 +621,18 @@ Glossary
Agendaless Consulting
A consulting organization formed by Paul Everitt, Tres Seaver,
and Chris McDonough. See also http://agendaless.com .
+
+ Jython
+ A `Python implementation <http://www.jython.org/>` written for
+ the Java Virtual Machine.
+
+ Python
+ The `programming language <http://python.org>` in which
+ :mod:`repoze.bfg` is written.
+
+ CPython
+ The C implementation of the Python language. This is the
+ reference implementation that most people refer to as simply
+ "Python"; :term:`Jython`, Google's App Engine, and `PyPy
+ <http://codespeak.net/pypy/dist/pypy/doc/>`_ are examples of
+ non-C based Python implementations.
diff --git a/docs/index.rst b/docs/index.rst
index 4ceba1818..7e727d410 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -45,7 +45,7 @@ Narrative documentation in chapter form explaining how to use
narr/introduction
narr/install
narr/configuration
- narr/scanning
+ narr/firstapp
narr/project
narr/startup
narr/urlmapping
diff --git a/docs/latexindex.rst b/docs/latexindex.rst
index 29c59dc25..e6d271724 100644
--- a/docs/latexindex.rst
+++ b/docs/latexindex.rst
@@ -13,9 +13,9 @@ Front Matter
:maxdepth: 1
copyright.rst
- thanks.rst
conventions.rst
foreword.rst
+ authorintro.rst
.. mainmatter::
@@ -28,7 +28,7 @@ Narrative Documentation
narr/introduction
narr/install
narr/configuration
- narr/scanning
+ narr/firstapp
narr/project
narr/urlmapping
narr/views
@@ -36,7 +36,6 @@ Narrative Documentation
narr/templates
narr/models
narr/security
- narr/hybrid
narr/vhosting
narr/events
narr/environment
diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst
index 11de4f741..08ca49710 100644
--- a/docs/narr/configuration.rst
+++ b/docs/narr/configuration.rst
@@ -1,48 +1,19 @@
-.. index::
- single: frameworks vs. libraries
- single: framework
-
.. _configuration_narr:
-Creating Your First :mod:`repoze.bfg` Application
-=================================================
-
-Most of the logic in a web application is completely
-application-specific. For example, the content of a web page served
-by one web application might be a representation of the contents of an
-accounting ledger, while the content of of a web page served by
-another might be a listing of songs. These applications probably
-won't serve the same set of customers. However, both the
-ledger-serving and song-serving applications can be written using
-:mod:`repoze.bfg`, because :mod:`repoze.bfg` is a very general
-*framework* which can be used to create all kinds of web applications.
-As a framework, the primary job of :mod:`repoze.bfg` is to make it
-easier for a developer to create an arbitrary web application.
-
-.. sidebar:: Frameworks vs. Libraries
-
- A *framework* differs from a *library* in one very important way:
- library code is always *called* by code that you write, while a
- framework always *calls* code that you write. Using a set of
- libraries to create an application is usually easier than using a
- framework initially, because you can choose to cede control to
- library code you have not authored very selectively. But when you
- use a framework, you are required to cede a greater portion of
- control to code you have not authored: code that resides in the
- framework itself. You needn't use a framework at all to create a
- web application using Python. A rich set of libraries already
- exists for the platform. In practice, however, using a framework
- to create an application is often more practical than rolling your
- own via a set of libraries if the framework provides a set of
- facilities that fits your application requirements.
+.. index::
+ single: application configuration
+
+Application Configuration
+=========================
Each deployment of an application written using :mod:`repoze.bfg`
implies a specific *configuration* of the framework itself. For
-example, a song-serving application might plug code into the framework
-that manages songs, while the ledger- serving application might plug
-in code that manages accounting information. :mod:`repoze.bfg` refers
-to the way in which code is plugged in to it for a specific
-application as "configuration".
+example, an application which serves up MP3s for user consumption
+might plug code into the framework that manages songs, while an
+application that application might plug in code that manages
+accounting information. :mod:`repoze.bfg` refers to the way in which
+code is plugged in to it for a specific application as
+"configuration".
Most people understand "configuration" as coarse knobs that inform the
high-level operation of a specific application deployment. For
@@ -55,24 +26,25 @@ code into the :mod:`repoze.bfg` framework, you are "configuring"
:mod:`repoze.bfg` for the purpose of creating a particular application
deployment.
-There are a number of different mechanisms you may use to configure
+There are two different mechanisms you may use to configure
:mod:`repoze.bfg` to create an application: *imperative* configuration
and *declarative* configuration. We'll examine both modes in the
sections which follow.
.. index::
- pair: imperative; helloworld
+ pair: imperative; configuration
-.. _helloworld_imperative:
+.. _imperative_configuration:
-Hello World, Configured Imperatively
-------------------------------------
+Imperative Configuration
+------------------------
-Experienced Python programmers might find that "imperative"
-configuration is easiest to use. This is the configuration mode in
-which a developer cedes the least amount of control to the framework;
-it's "imperative" because you express the configuration directly in
-Python code.
+Experienced Python programmers might find that performing
+configuration "imperatively" fits their brain best. This is the
+configuration mode in which a developer cedes the least amount of
+control to the framework; it's "imperative" because you express the
+configuration directly in Python code, and you have the full power of
+Python at your disposal as you perform configuration statements.
Here's one of the simplest :mod:`repoze.bfg` applications, configured
imperatively:
@@ -87,491 +59,45 @@ imperatively:
def hello_world(request):
return Response('Hello world!')
- def goodbye_world(request):
- return Response('Goodbye world!')
-
if __name__ == '__main__':
config = Configurator()
config.begin()
config.add_view(hello_world)
- config.add_view(goodbye_world, name='goodbye')
config.end()
app = config.make_wsgi_app()
serve(app)
-When this code is inserted into a Python script named
-``helloworld.py`` and executed by a Python interpreter which has the
-:mod:`repoze.bfg` software installed, an HTTP server is started on
-port 8080. When port 8080 is visited by a user agent on the root URL
-(``/``), the server will simply serve up the text "Hello world!" with
-the HTTP response values ``200 OK`` as a response code and a
-``Content-Type`` header value of ``text/plain``. But for reasons
-we'll better understand shortly, when visited by a user agent on the
-URL ``/goodbye``, the server will serve up "Goodbye world!".
-
-Let's examine this program piece-by-piece.
-
-Imports
-~~~~~~~
-
-The above script defines the following set of imports:
-
-.. code-block:: python
- :linenos:
-
- from webob import Response
- from paste.httpserver import serve
- from repoze.bfg.configuration import Configurator
-
-:mod:`repoze.bfg` uses the :term:`WebOb` library as the basis for its
-:term:`request` and :term:`response` objects. The script uses the
-:class:`webob.Response` class later in the script to create a
-:term:`response` object.
-
-Like many other Python web frameworks, :mod:`repoze.bfg` uses the
-:term:`WSGI` protocol to connect an application and a web server
-together. The :mod:`paste.httpserver` server is used in this example
-as a WSGI server for convenience, as ``Paste`` is a dependency of
-:mod:`repoze.bfg` itself. However, :mod:`repoze.bfg` applications can
-be served by any WSGI server.
-
-The script also imports the ``Configurator`` class from the
-``repoze.bfg.configuration`` module. This class is used to configure
-:mod:`repoze.bfg` for a particular application. An instance of this
-class provides methods which help configure various parts of
-:mod:`repoze.bfg` for a given application deployment.
-
-View Declaration
-~~~~~~~~~~~~~~~~
-
-The above script, beneath its set of imports, defines two functions:
-one named ``hello_world`` and one named ``goodbye_world``.
-
-.. code-block:: python
- :linenos:
-
- def hello_world(request):
- return Response('Hello world!')
-
- def goodbye_world(request):
- return Response('Goodbye world!')
-
-Each function accepts a single argument (``request``) and returns an
-instance of the :class:`webob.Response` class. In the ``hello_world``
-function, the string ``'Hello world!'`` is passed to the ``Response``
-constructor as the *body* of the response. In the ``goodbye_world``
-function, the string ``'Goodbye world!'`` is passed.
-
-Each of these functions is known as a :term:`view callable`. View
-callables in a "real" :mod:`repoze.bfg` application are often
-functions which accept a :term:`request` and return a
-:term:`response`. A view callable can be represented via another type
-of object, like a class or an instance, but for our purposes here, a
-function serves us well.
-
-A view callable is called with a :term:`request` object, which is a
-representation of an HTTP request sent by a remote user agent. A view
-callable is required to return a :term:`response` object because a
-response object has all the information necessary to formulate an
-actual HTTP response; this object is then converted to text and sent
-back to the requesting user agent.
-
-The ``hello_world`` view callable defined by the script does nothing
-but return a response with the body ``Hello world!``; the
-``goodbye_world`` view callable returns a response with the body
-``Goodbye world!``.
-
-.. index::
- pair: traversal; introduction
-
-.. _traversal_intro:
-
-An Introduction to Traversal
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you've run the code listed in :ref`helloworld_imperative` already,
-you've unwittingly configured :mod:`repoze.bfg` to serve an
-application that relies on :term:`traversal`. A full explanation of
-how :mod:`repoze.bfg` locates "the right" :term:`view callable` for a
-given request requires some explanation of :term:`traversal`.
-
-Traversal is part of a mechanism used by :mod:`repoze.bfg` to map the
-URL of some request to a particular :term:`view callable`. It is not
-the only mechanism made available by :mod:`repoze.bfg` that allows the
-mapping a URL to a view callable. Another distinct mode known as
-:term:`URL dispatch` can alternately be used to find a view callable
-based on a URL. However, our sample application uses only
-:term:`traversal`.
-
-In :mod:`repoze.bfg` terms, :term:`traversal` is the act of walking
-over an object graph starting from a :term:`root` object in order to
-find a :term:`context` object and a :term:`view name`. Once a context
-and a view name are found, these two bits of information, plus other
-information from the request are used to look up a :term:`view
-callable`. :mod:`repoze.bfg` bothers to do traversal only because the
-information returned from traversal allows a view callable to be
-found.
-
-The individual path segments of the "path info" portion of a URL (the
-data following the hostname and port number, but before any query
-string elements or fragments, for example the ``/a/b/c`` portion of
-the URL ``http://example.com/a/b/c?foo=1``) are used as "steps" during
-traversal.
-
-.. note:: A useful analogy of how :mod:`repoze.bfg` :term:`traversal`
- works is available within the chapter section entitled
- :ref:`traversal_behavior`.
-
-The results of a :term:`traversal` include a :term:`context` and a
-:term:`view name`. The :term:`view name` is the *first* URL path
-segment in the set of path segments "left over" in the results of
-:term:`traversal`. This will either be the empty string (``''``) or a
-non-empty string (one of the path segment strings). The empty string
-represents the :term:`default view` of a context object.
-
-The :term:`default view` is found when all path elements in the URL
-are exhausted before :term:`traversal` returns a :term:`context`
-object, causing the :term:`view name` to be ``''`` (the empty string).
-When no path segments are "left over" after traversal, the
-:term:`default view` for the context found is invoked.
-
-If traversal returns a non-empty :term:`view name`, it means that
-traversal "ran out" of nodes in the graph before it finished
-exhausting all the path segments implied by the path info of the URL:
-no segments are "left over". In this case, because the :term:`view
-name` is non-empty, a *non-default* view callable will be invoked.
-
-The combination of the :term:`context` object and the :term:`view
-name` (and, in more complex configurations, other :term:`predicate`
-values) are used to find "the right" :term:`view callable`, which will
-be invoked after traversal.
-
-The object graph of our hello world application is very simple:
-there's exactly one object in our graph; the default :term:`root`
-object.
-
-Relating Traversal to the Hello World Application
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Our application's :term:`root` object is the *default* root object
-used when one isn't otherwise specified in application configuration.
-This root object does not have a ``__getitem__`` method, thus it has
-no children. Although in a more complex system there can be many
-contexts which URLs resolve to in our application, effectively there
-is only ever one context: the root object.
-
-We have only a single default view registered (the registration for
-the ``hello_world`` view callable). Due to this set of circumstances,
-you can consider the sole possible URL that will resolve to a
-:term:`default view` in this application the root URL ``'/'``. It is
-the only URL that will resolve to the :term:`view name` of ``''`` (the
-empty string).
-
-We have only a single view registered for the :term:`view name`
-``goodbye`` (the registration for the ``goodbye_world`` view
-callable). Due to this set of circumstances, you can consider the
-sole possible URL that will resolve to the ``goodbye_world`` in this
-application the URL ``'/goodbye'`` because it is the only URL that
-will resolve to the :term:`view name` of ``goodbye``.
-
-.. index::
- pair: imperative; configuration
- single: Configurator
-
-.. _helloworld_imperative_appconfig:
-
-Application Configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In the above script, the following code, representing the
-*configuration* of an application which uses the previously defined
-imports and function definitions is placed within the confines of an
-``if`` statement:
-
-.. code-block:: python
- :linenos:
-
- if __name__ == '__main__':
- config = Configurator()
- config.begin()
- config.add_view(hello_world)
- config.add_view(goodbye_world, name='goodbye')
- config.end()
- app = config.make_wsgi_app()
- simple_server.make_server('', 8080, app).serve_forever()
-
-Let's break this down this piece-by-piece.
-
-Configurator Construction
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. code-block:: python
- :linenos:
-
- if __name__ == '__main__':
- config = Configurator()
-
-The ``if __name__ == '__main__':`` line above represents a Python
-idiom: the code inside this if clause is not invoked unless the script
-is run directly from the command line via, for example, ``python
-helloworld.py`` where the file named ``helloworld.py`` contains the
-entire script body. ``helloworld.py`` in this case is a Python
-*module*. Using the ``if`` clause is necessary (or at least "best
-practice") because code in any Python module may be imported by
-another Python module. By using this idiom, the script is indicating
-that it does not want the code within the ``if`` statement to execute
-if this module is imported; the code within the ``if`` block should
-only be run during a direct script execution.
-
-The ``config = Configurator()`` line above creates an instance of the
-:class:`repoze.bfg.configuration.Configurator` class. The resulting
-``config`` object represents an API which the script uses to configure
-this particular :mod:`repoze.bfg` application.
-
-.. note::
-
- An instance of the :class:`repoze.bfg.configuration.Configurator`
- class is a *wrapper* object which mutates an :term:`application
- registry` as its methods are called. An application registry
- represents the configuration state of a :mod:`repoze.bfg`
- application. The ``Configurator`` is not itself an
- :term:`application registry`, it is a mechanism used to configure
- an application registry. The underlying application registry
- object being configured by a ``Configurator`` is available as its
- ``registry`` attribute.
-
-Beginning Configuration
-~~~~~~~~~~~~~~~~~~~~~~~
-
-.. ignore-next-block
-.. code-block:: python
-
- config.begin()
-
-The :meth:`repoze.bfg.configuration.Configurator.begin` method tells
-the the system that application configuration has begun. In
-particular, this causes the :term:`application registry` associated
-with this configurator to become the "current" application registry,
-meaning that code which attempts to use the application registry
-:term:`thread local` will obtain the registry associated with the
-configurator. This is an explicit step because it's sometimes
-convenient to use a configurator without causing the registry
-associated with the configurator to become "current".
-
-Adding Configuration
-~~~~~~~~~~~~~~~~~~~~
-
-.. ignore-next-block
-.. code-block:: python
- :linenos:
-
- config.add_view(hello_world)
- config.add_view(goodbye_world, name='goodbye')
-
-Each of these lines calls the
-:meth:`repoze.bfg.configuration.Configurator.add_view` method. The
-``add_view`` method of a configurator registers a :term:`view
-configuration` within the :term:`application registry`. A :term:`view
-configuration` represents a :term:`view callable` which must be
-invoked when a set of circumstances related to the :term:`request` is
-true. This "set of circumstances" is provided as one or more keyword
-arguments to the ``add_view`` method, otherwise known as
-:term:`predicate` arguments.
-
-The line ``config.add_view(hello_world)`` registers the
-``hello_world`` function as a view callable. The ``add_view`` method
-of a Configurator must be called with a view callable object as its
-first argument, so the first argument passed is ``hello_world``
-function we'd like to use as a :term:`view callable`. However, this
-line calls ``add_view`` with a single default :term:`predicate`
-argument, the ``name`` predicate with a value of ``''``, meaning that
-we'd like :mod:`repoze.bfg` to invoke the ``hello_world`` view
-callable for any request for the :term:`default view` of an object.
-
-Our ``hello_world`` :term:`view callable` returns a Response instance
-with a body of ``Hello world!`` in the configuration implied by this
-script. It is configured as a :term:`default view`. Therefore, a
-user agent contacting a server running this application will receive
-the greeting ``Hello world!`` when any :term:`default view` is
-invoked.
-
-.. sidebar:: View Dispatch and Ordering
-
- When :term:`traversal` is used, :mod:`repoze.bfg` chooses the most
- specific view callable based *only* on view :term:`predicate`
- applicability. This is unlike :term:`URL dispatch`, another
- dispatch mode of :mod:`repoze.bfg` (and other frameworks, like
- :term:`Pylons` and :term:`Django`) which first uses an ordered
- routing lookup to resolve the request to a view callable by running
- it through a relatively-ordered series of URL path matches. We're
- not really concerned about the finer details of :term:`URL
- dispatch` right now. It's just useful to use for demonstrative
- purposes: the ordering of calls to
- :meth:`repoze.bfg.configuration.Configurator.add_view`` is never
- very important. We can register ``goodbye_world`` first and
- ``hello_world`` second; :mod:`repoze.bfg` will still give us the
- most specific callable when a request is dispatched to it.
-
-The line ``config.add_view(goodbye_world, name='goodbye')`` registers
-the ``goodbye_world`` function as a view callable. The line calls
-``add_view`` with the view callable as the first required positional
-argument, and a :term:`predicate` keyword argument ``name`` with the
-value ``'goodbye'``. This :term:`view configuration` implies that a
-request with a :term:`view name` of ``goodbye`` should cause the
-``goodbye_world`` view callable to be invoked. For the purposes of
-this discussion, the :term:`view name` can be considered the first
-non-empty path segment in the URL: in particular, this view
-configuration will match when the URL is ``/goodbye``.
-
-Our ``goodbye_world`` :term:`view callable` returns a Response
-instance with a body of ``Goodbye world!`` in the configuration
-implied by this script. It is configured as with a :term:`view name`
-predicate of ``goodbye``. Therefore, a user agent contacting a server
-running this application will receive the greeting ``Goodbye world!``
-when the path info part of the request is ``/goodbye``.
-
-Each invocation of the ``add_view`` method implies a :term:`view
-configuration` registration. Each :term:`predicate` provided as a
-keyword argument to the ``add_view`` method narrows the set of
-circumstances which would cause the view configuration's callable to
-be invoked. In general, a greater number of predicates supplied along
-with a view configuration will more strictly limit the applicability
-of its associated view callable. When :mod:`repoze.bfg` processes a
-request, however, the view callable with the *most specific* view
-configuration (the view configuration that matches the largest number
-of predicates) is always invoked.
-
-Earlier we explained that the server would return ``Hello world!`` if
-you visited the *root* (``/``) URL. However, actually, because the
-view configuration registration for the ``hello_world`` view callable
-has no :term:`predicate` arguments, the ``hello_world`` view callable
-is applicable for the :term:`default view` of any :term:`context`
-resulting from a request. This isn't all that interesting in this
-application, because we always only have *one* potential context (the
-root object): it is the only object in the graph.
-
-We've also registered a view configuration for another circumstance:
-the ``goodbye_world`` view callable has a ``name`` predicate of
-``goodbye``, meaning that it will match for requests that have the
-:term:`view name` ``goodbye`` unlike the ``hello_world`` view
-configuration registration, which will only match the default view
-(view name ``''``) of a request. Because :mod:`repoze.bfg` chooses
-the best view configuration for any request, the ``goodbye_world``
-view callable will be used when the URL contains path information that
-ends with ``/goodbye``.
-
-Ending Configuration
-~~~~~~~~~~~~~~~~~~~~
-
-.. ignore-next-block
-.. code-block:: python
-
- config.end()
-
-The :meth:`repoze.bfg.configuration.Configurator.end` method tells the
-the system that application configuration has ended. It is the
-inverse of :meth:`repoze.bfg.configuration.Configurator.begin`. In
-particular, this causes the :term:`application registry` associated
-with this configurator to no longer be the "current" application
-registry, meaning that code which attempts to use the application
-registry :term:`thread local` will no longer obtain the registry
-associated with the configurator.
-
-.. index::
- single: make_wsgi_app
- pair: WSGI; application
- triple: WSGI; application; creation
-
-WSGI Application Creation
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. ignore-next-block
-.. code-block:: python
-
- app = config.make_wsgi_app()
-
-After configuring views and ending configuration, the script creates a
-WSGI *application* via the
-:meth:`repoze.bfg.configuration.Configurator.make_wsgi_app` method. A
-call to ``make_wsgi_app`` implies that all configuration is finished
-(meaning all method calls to the configurator which set up views, and
-various other configuration settings have been performed). The
-``make_wsgi_app`` method returns a :term:`WSGI` application object
-that can be used by any WSGI server to present an application to a
-requestor.
-
-The :mod:`repoze.bfg` application object, in particular, is an
-instance of the :class:`repoze.bfg.router.Router` class. It has a
-reference to the :term:`application registry` which resulted from
-method calls to the configurator used to configure it. The Router
-consults the registry to obey the policy choices made by a single
-application. These policy choices were informed by method calls to
-the ``Configurator`` made earlier; in our case, the only policy
-choices made were implied by two calls to the ``add_view`` method,
-telling our application that it should effectively serve up the
-``hello_world`` view callable to any user agent when it visits the
-root URL, and the ``goodbye_world`` view callable to any user agent
-when it visits the URL with the path info ``/goodbye``.
-
-WSGI Application Serving
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. ignore-next-block
-.. code-block:: python
-
- serve(app)
-
-Finally, we actually serve the application to requestors by starting
-up a WSGI server. We happen to use the :func:`paste.httpserver.serve`
-WSGI server runner, using the default TCP port of 8080, and we pass it
-the ``app`` object (an instance of the
-:class:`repoze.bfg.router.Router` class) as the application we wish to
-serve. This causes the server to start listening on the TCP port. It
-will serve requests forever, or at least until we stop it by killing
-the process which runs it.
-
-Conclusion
-~~~~~~~~~~
-
-Our hello world application is one of the simplest possible
-:mod:`repoze.bfg` applications, configured "imperatively". We can see
-a good deal of what's going on "under the hood" when we configure a
-:mod:`repoze.bfg` application imperatively. However, another mode of
-configuration exists named *declarative* configuration.
+We won't talk much about what this application does yet. Just note
+that the "configuration' statements take place underneath the ``if
+__name__ == '__main__':`` stanza in the form of method calls on a
+:term:`Configurator` object (e.g. ``config.begin()``,
+``config.add_view(...)``, and ``config.end()``. These statements take
+place one after the other, and are executed in order, so the full
+power of Python, including conditionals, can be employed in this mode
+of configuration.
.. index::
- pair: helloworld; declarative
- single: helloworld
+ pair: declarative; configuration
-.. _helloworld_declarative:
+.. _declarative_configuration:
-Hello World, Configured Declaratively
--------------------------------------
+Declarative Configuration
+-------------------------
-:mod:`repoze.bfg` can be configured for the same "hello world"
-application "declaratively", if so desired. Declarative configuration
-relies on *declarations* made external to the code in a configuration
-file format named :term:`ZCML` (Zope Configuration Markup Language),
-an XML dialect.
+A :mod:`repoze.bfg` application can be alternatively be configured
+"declaratively", if so desired. Declarative configuration relies on
+*declarations* made external to the code in a configuration file
+format named :term:`ZCML` (Zope Configuration Markup Language), an XML
+dialect.
Declarative configuration mode is the configuration mode in which
developers cede the most amount of control to the framework itself.
Because application developers cede more control to the framework, it
-is also harder to understand than purely imperative configuration.
-However, using declarative configuration has a number of benefits, the
-primary benefit being that applications configured declaratively can
-be *overridden* and *extended* by third parties without requiring the
-third party to change application code.
-
-.. note::
+is also sometimes harder to understand than purely imperative
+configuration.
- See :ref:`extending_chapter` for a discussion of extending and
- overriding :mod:`repoze.bfg` applications.
-
-Unlike the simplest :mod:`repoze.bfg` application configured
-imperatively, the simplest :mod:`repoze.bfg` application, configured
-declaratively requires not one, but two files: a Python file and a
-:term:`ZCML` file.
+A :mod:`repoze.bfg` application configured declaratively requires not
+one, but two files: a Python file and a :term:`ZCML` file.
In a file named ``helloworld.py``:
@@ -585,9 +111,6 @@ In a file named ``helloworld.py``:
def hello_world(request):
return Response('Hello world!')
- def goodbye_world(request):
- return Response('Goodbye world!')
-
if __name__ == '__main__':
config = Configurator()
config.begin()
@@ -610,19 +133,16 @@ previously created ``helloworld.py``:
view="helloworld.hello_world"
/>
- <view
- name="goodbye"
- view="helloworld.goodbye_world"
- />
-
</configure>
This pair of files forms an application functionally equivalent to the
-application we created earlier. Let's examine the differences between
-the code described in :ref:`helloworld_imperative` and the code above.
+application we created earlier in :ref:`imperative_configuration`.
+
+Let's examine the differences between the code listing in
+:ref:`imperative_configuration` and the code above.
-In :ref:`helloworld_imperative_appconfig`, we had the following lines
-within the ``if __name__ == '__main__'`` section of ``helloworld.py``:
+In :ref:`imperative_configuration`, we had the following lines within
+the ``if __name__ == '__main__'`` section of ``helloworld.py``:
.. code-block:: python
:linenos:
@@ -631,16 +151,14 @@ within the ``if __name__ == '__main__'`` section of ``helloworld.py``:
config = Configurator()
config.begin()
config.add_view(hello_world)
- config.add_view(goodbye_world, name='goodbye')
config.end()
app = config.make_wsgi_app()
- simple_server.make_server('', 8080, app).serve_forever()
+ serve(app)
-In our "declarative" code, we've added a call to the
-:meth:`repoze.bfg.configuration.Configurator.load_zcml` method with
-the value ``configure.zcml``, and we've removed the lines which read
-``config.add_view(hello_world)`` and ``config.add_view(goodbye_world,
-name='goodbye')``, so that it now reads as:
+In our "declarative" code, we've removed the call to ``add_view`` and
+replaced it with a call to the
+:meth:`repoze.bfg.configuration.Configurator.load_zcml` method so that
+it now reads as:
.. code-block:: python
:linenos:
@@ -651,14 +169,14 @@ name='goodbye')``, so that it now reads as:
config.load_zcml('configure.zcml')
config.end()
app = config.make_wsgi_app()
- simple_server.make_server('', 8080, app).serve_forever()
+ serve(app)
Everything else is much the same.
The ``config.load_zcml('configure.zcml')`` line tells the configurator
-to load configuration declarations from the ``configure.zcml`` file
-which sits next to ``helloworld.py``. Let's take a look at the
-``configure.zcml`` file now:
+to load configuration declarations from the file named
+``configure.zcml`` which sits next to ``helloworld.py`` on the
+filesystem. Let's take a look at that ``configure.zcml`` file again:
.. code-block:: xml
:linenos:
@@ -671,166 +189,45 @@ which sits next to ``helloworld.py``. Let's take a look at the
view="helloworld.hello_world"
/>
- <view
- name="goodbye"
- view="helloworld.goodbye_world"
- />
-
</configure>
-The ``<configure>`` Tag
-~~~~~~~~~~~~~~~~~~~~~~~
-
-The ``configure.zcml`` ZCML file contains this bit of XML:
-
-.. code-block:: xml
- :linenos:
-
- <configure xmlns="http://namespaces.repoze.org/bfg">
-
- <!-- other directives -->
-
- </configure>
-
-Because :term:`ZCML` is XML, and because XML requires a single root
-tag for each document, every ZCML file used by :mod:`repoze.bfg` must
-contain a ``configure`` container directive, which acts as the root
-XML tag. It is a "container" directive because its only job is to
-contain other directives.
-
-See also :ref:`configure_directive` and :ref:`word_on_xml_namespaces`.
-
-The ``<include>`` Tag
-~~~~~~~~~~~~~~~~~~~~~
-
-The ``configure.zcml`` ZCML file contains this bit of XML within the
-``<configure>`` root tag:
-
-.. code-block:: xml
-
- <include package="repoze.bfg.includes" />
-
-This singleton (self-closing) tag instructs ZCML to load a ZCML file
-from the Python package with the :term:`dotted Python name`
-:mod:`repoze.bfg.includes`, as specified by its ``package`` attribute.
-This particular ``<include>`` declaration is required because it
-actually allows subsequent declaration tags (such as ``<view>``, which
-we'll see shortly) to be recognized. The ``<include>`` tag
-effectively just includes another ZCML file; this causes its
-declarations to be executed. In this case, we want to load the
-declarations from the file named ``configure.zcml`` within the
-:mod:`repoze.bfg.includes` Python package. We know we want to load
-the ``configure.zcml`` from this package because ``configure.zcml`` is
-the default value for another attribute of the ``<include>`` tag named
-``file``. We could have spelled the include tag more verbosely, but
-equivalently as:
-
-.. code-block:: xml
- :linenos:
-
- <include package="repoze.bfg.includes"
- file="configure.zcml"/>
+Note that this file contains some XML, and that the XML contains a
+``<view>`` :term:`configuration declaration` tag that references a
+:term:`dotted Python name`. This dotted name refers to the
+``hello_world`` function that lives in our ``helloword`` Python
+module.
-The ``<include>`` tag that includes the ZCML statements implied by the
-``configure.zcml`` file from the Python package named
-:mod:`repoze.bfg.includes` is basically required to come before any
-other named declaration in an application's ``configure.zcml``. If it
-is not included, subsequent declaration tags will fail to be
-recognized, and the configuration system will generate a traceback.
-However, the ``<include package="repoze.bfg.includes"/>`` tag needs to
-exist only in a "top-level" ZCML file, it needn't also exist in ZCML
-files *included by* a top-level ZCML file.
+This ``<view>`` declaration tag performs the same function as the
+``add_view`` method that was employed within
+:ref:`imperative_configuration`. In fact, the ``<view>`` tag is
+effectively a "macro" which calls the
+:meth:`repoze.bfg.configuration.Configurator.add_view` method on your
+behalf.
-See also :ref:`include_directive`.
-
-The ``<view>`` Tag
-~~~~~~~~~~~~~~~~~~
-
-The ``configure.zcml`` ZCML file contains these bits of XML *after* the
-``<include>`` tag, but *within* the ``<configure>`` root tag:
-
-.. code-block:: xml
- :linenos:
-
- <view
- view="helloworld.hello_world"
- />
-
- <view
- name="goodbye"
- view="helloworld.goodbye_world"
- />
-
-These ``<view>`` declaration tags direct :mod:`repoze.bfg` to create
-two :term:`view configuration` registrations. The first ``<view>``
-tag has an attribute (the attribute is also named ``view``), which
-points at a :term:`dotted Python name`, referencing the
-``hello_world`` function defined within the ``helloworld`` package.
-The second ``<view>`` tag has a ``view`` attribute which points at a
-:term:`dotted Python name`, referencing the ``goodbye_world`` function
-defined within the ``helloworld`` package. The second ``<view>`` tag
-also has an attribute called ``name`` with a value of ``goodbye``.
-
-These effect of the ``<view>`` tag declarations we've put into our
-``configure.zcml`` is functionally equivalent to the effect of lines
-we've already seen in an imperatively-configured application. We're
-just spelling things differently, using XML instead of Python.
-
-In our previously defined application, in which we added view
-configurations imperatively, we saw this code:
-
-.. ignore-next-block
-.. code-block:: python
- :linenos:
-
- config.add_view(hello_world)
- config.add_view(goodbye_world, name='goodbye')
-
-Each ``<view>`` declaration tag encountered in a ZCML file effectively
-invokes the :meth:`repoze.bfg.configuration.Configurator.add_view`
-method on the behalf of the developer. Various attributes can be
-specified on the ``<view>`` tag which influence the :term:`view
-configuration` it creates.
-
-Since the relative ordering of calls to
-:meth:`repoze.bfg.configuration.Configurator.add_view` doesn't matter
-(see the sidebar entitled *View Dispatch and Ordering*), the relative
-order of ``<view>`` tags in ZCML doesn't matter either. The following
-ZCML orderings are completely equivalent:
-
-.. topic:: Hello Before Goodbye
-
- .. code-block:: xml
- :linenos:
-
- <view
- view="helloworld.hello_world"
- />
-
- <view
- name="goodbye"
- view="helloworld.goodbye_world"
- />
+The ``<view>`` tag is an example of a :mod:`repoze.bfg` declaration
+tag. Other such tags include ``<route>``, ``<scan>``, ``<notfound>``,
+``<forbidden>``, and others. Each of these tags is effectively a
+"macro" which calls methods of a
+:class:`repoze.bfg.configuration.Configurator` object on your behalf.
-.. topic:: Goodbye Before Hello
+Essentially, using a :term:`ZCML` file and loading it from the
+filesystem allows us to put our configuration statements within this
+XML file rather as declarations, rather than representing them as
+method calls to a :term:`Configurator` object. Otherwise, declarative
+and imperative configuration are functionally equivalent.
- .. code-block:: xml
- :linenos:
+Using declarative configuration has a number of benefits, the primary
+benefit being that applications configured declaratively can be
+*overridden* and *extended* by third parties without requiring the
+third party to change application code.
- <view
- name="goodbye"
- view="helloworld.goodbye_world"
- />
+.. note::
- <view
- view="helloworld.hello_world"
- />
+ See :ref:`extending_chapter` for a discussion of extending and
+ overriding :mod:`repoze.bfg` applications.
-The ``<view>`` tag is an example of a :mod:`repoze.bfg` declaration
-tag. Other such tags include ``<route>``, ``<scan>``, ``<notfound>``,
-``<forbidden>``, and others. Each of these tags is effectively a
-"macro" which calls methods on the
-:class:`repoze.bfg.configuration.Configurator` object on your behalf.
+If you want to build a framework or an extensible application, using
+ZCML is a good idea.
.. index::
pair: ZCML; conflict detection
@@ -838,8 +235,8 @@ tag. Other such tags include ``<route>``, ``<scan>``, ``<notfound>``,
ZCML Conflict Detection
~~~~~~~~~~~~~~~~~~~~~~~
-An additional feature of ZCML is *conflict detection*. If you define
-two declaration tags within the same ZCML file which logically
+A minor additional feature of ZCML is *conflict detection*. If you
+define two declaration tags within the same ZCML file which logically
"collide", an exception will be raised, and the application will not
start. For example, the following ZCML file has two conflicting
``<view>`` tags:
@@ -862,33 +259,155 @@ start. For example, the following ZCML file has two conflicting
</configure>
If you try to use this ZCML file as the source of ZCML for an
-application, a :data:`repoze.bfg.exceptions.ConfigurationError` will
-be raised when you attempt to start the application with information
-about which tags might have conflicted.
+application, an error will be raised when you attempt to start the
+application. This error will contain information about which tags
+might have conflicted.
+
+.. index::
+ single: bfg_view
+ pair: ZCML directive; view
+ single: configuration decorations
+ pair: code; scanning
+
+.. _decorations_and_code_scanning:
+
+Configuration Decorations and Code Scanning
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+An alternate mode of declarative configuration lends more *locality of
+reference* to a :term:`configuration declaration`. It's sometimes
+painful to have all configuration done in ZCML, or even in imperative
+code, because you may need to have two files open at once; the file
+that represents the configuration, and the file that contains the
+implementation objects (such as :term:`view callable` functions) that
+the configuration references. To avoid this, :mod:`repoze.bfg` allows
+you to insert :term:`configuration decoration` statements very close
+to code that is referred to by the declaration itself. For example:
+
+.. code-block:: python
+ :linenos:
-Conclusions
------------
+ from repoze.bfg.view import bfg_view
+ from webob import Response
-.. sidebar:: Which Configuration Mode Should I Use?
+ @bfg_view(name='hello', request_method='GET')
+ def hello(request):
+ return Response('Hello')
+
+The :class:`repoze.bfg.view.bfg_view` decorator above adds an
+attribute to the ``hello`` function, making it available for a
+:term:`scan` to find it later.
+
+The mere existence of configuration decoration doesn't cause any
+configuration registration to be made. Before they have any effect on
+the configuration of a :mod:`repoze.bfg` application, a configuration
+decoration within application code must be found through a process
+known as *scanning*.
+
+:mod:`repoze.bfg` is willing to :term:`scan` a module or a package and
+its subpackages for decorations when the
+:meth:`repoze.bfg.configuration.Configurator.scan` method is invoked:
+scanning implies searching for configuration declarations in a package
+and its subpackages. For example:
+
+.. topic:: Imperatively Starting A Scan
+
+ .. code-block:: python
+ :linenos:
+
+ from paste.httpserver import serve
+ from repoze.bfg.view import bfg_view
+ from webob import Response
+
+ @bfg_view()
+ def hello(request):
+ return Response('Hello')
+
+ if __name__ == '__main__':
+ from repoze.bfg.configuration import Configurator
+ config = Configurator()
+ config.begin()
+ config.scan()
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+:term:`ZCML` can also invoke a :term:`scan` via its ``<scan>``
+directive. If a ZCML file is processed that contains a scan
+directive, the package the ZCML file points to is scanned.
+
+.. topic:: Declaratively Starting a Scan
+
+ .. code-block:: python
+ :linenos:
+
+ # helloworld.py
+
+ from paste.httpserver import serve
+ from repoze.bfg.view import bfg_view
+ from webob import Response
+
+ @bfg_view()
+ def hello(request):
+ return Response('Hello')
+
+ if __name__ == '__main__':
+ from repoze.bfg.configuration import Configurator
+ config = Configurator()
+ config.begin()
+ config.load_zcml('configure.zcml')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+ .. code-block:: xml
+ :linenos:
+
+ <configure xmlns="http://namespaces.repoze.org">
+
+ <!-- configure.zcml -->
+
+ <include package="repoze.bfg.includes"/>
+ <scan package="."/>
+
+ </configure>
+
+The scanning machinery imports each module and subpackage in a package
+or module recursively, looking for special attributes attached to
+objects defined within a module. These special attributes are
+typically attached to code via the use of a :term:`decorator`. For
+example, the :class:`repoze.bfg.view.bfg_view` decorator can be
+attached to a function or instance method:
+
+Once scanning is invoked, and :term:`configuration decoration` is
+found by the scanner, a set of calls are made to a
+:term:`Configurator` on behalf of the developer: these calls represent
+the intent of the configuration decoration.
+
+In the example above, this is best represented as the scanner
+translating the arguments to :class:`repoze.bfg.view.bfg_view` into a
+call to the :meth:`repoze.bfg.configuration.Configurator.add_view`
+method, effectively:
- We recommend declarative configuration (:term:`ZCML`), because it's
- the more traditional form of configuration used by Zope-based
- systems, it can be overridden and extended by third party deployers,
- and there are more examples for it "in the wild". However,
- imperative mode configuration can be simpler to understand.
+.. ignore-next-block
+.. code-block:: python
-:mod:`repoze.bfg` allows an application to perform configuration tasks
-either imperatively or declaratively. You can choose the mode that
-best fits your brain as necessary.
+ config.add_view(hello)
-For more information about the API of a ``Configurator`` object, see
-:class:`repoze.bfg.configuration.Configurator` . The equivalent ZCML
-declaration tags are introduced in narrative documentation chapters as
-necessary.
+Which Mode Should I Use?
+------------------------
-For more information about :term:`traversal`, see
-:ref:`traversal_chapter`.
+A combination of imperative configuration, declarative configuration
+via ZCML and scanning can be used to configure any application. They
+are not mutually exclusive.
-For more information about :term:`view configuration`, see
-:ref:`views_chapter`.
+The :mod:`repoze.bfg` authors often recommend using mostly declarative
+configuration, because it's the more traditional form of configuration
+used in :mod:`repoze.bfg` applications, it can be overridden and
+extended by third party deployers, and there are more examples for it
+"in the wild".
+However, imperative mode configuration can be simpler to understand,
+and the framework is not "opinionated" about the choice. This book
+presents examples in both styles, mostly interchangeably. You can
+choose the mode that best fits your brain as necessary.
diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst
index b43aefc2f..ac13dd9c6 100644
--- a/docs/narr/environment.rst
+++ b/docs/narr/environment.rst
@@ -197,10 +197,10 @@ changing the content of an overridden resource directory for templates
without restarting the server after every change. Subsequent requests
for the same template file may return different filenames based on the
current state of overridden resource directories. Setting
-``reload_resources`` to ``True`` effects performance *dramatically*
-(slowing things down by an order of magnitude for each template
-rendering) but it's convenient when moving files around in overridden
-resource directories. ``reload_resources`` makes the system *very
-slow* when templates are in use. Never set ``reload_resources`` to
-``True`` on a production system.
+``reload_resources`` to ``True`` effects performance *dramatically*,
+slowing things down by an order of magnitude for each template
+rendering. However, it's convenient to enable when moving files
+around in overridden resource directories. ``reload_resources`` makes
+the system *very slow* when templates are in use. Never set
+``reload_resources`` to ``True`` on a production system.
diff --git a/docs/narr/events.rst b/docs/narr/events.rst
index 1d012b99d..a0142f187 100644
--- a/docs/narr/events.rst
+++ b/docs/narr/events.rst
@@ -13,11 +13,10 @@ Using Events
An *event* is an object broadcast by the :mod:`repoze.bfg` framework
at interesting points during the lifetime of an application. You
-don't need to use, know about, or care about events in order to create
-most :mod:`repoze.bfg` applications, but they can be useful when you
-want to perform slightly advanced operations. For example,
-subscribing to an event can allow you to "skin" a site slightly
-differently based on the hostname used to reach the site.
+don't need to use events in order to create most :mod:`repoze.bfg`
+applications, but they can be useful when you want to perform slightly
+advanced operations. For example, subscribing to an event can allow
+you to run some code as the result of every new request.
Events in :mod:`repoze.bfg` are always broadcast by the framework.
However, they only become useful when you register a *subscriber*. A
@@ -34,8 +33,10 @@ when it's called.
The mere existence of a subscriber function, however, is not
sufficient to arrange for it to be called. To arrange for the
-subscriber to be called, you'll need to change your :term:`application
-registry` by either of the following methods:
+subscriber to be called, you'll need to use the
+:meth:`repoze.bfg.configurator.Configurator.add_subscriber` method to
+register the subscriber imperatively, or you'll need to use ZCML for
+the same purpose:
.. topic:: Configuring an Event Listener Imperatively
@@ -51,6 +52,9 @@ registry` by either of the following methods:
from subscribers import mysubscriber
+ # "config" below is assumed to be an instance of a
+ # repoze.bfg.configuration.Configurator object
+
config.add_subscriber(mysubscriber, INewRequest)
The first argument to
@@ -73,7 +77,9 @@ registry` by either of the following methods:
handler=".subscribers.mysubscriber"
/>
-Each of the above examples implies that every time the
+ See also :ref:`subscriber_directive`.
+
+Either of the above registration examples implies that every time the
:mod:`repoze.bfg` framework emits an event object that supplies an
:class:`repoze.bfg.interfaces.INewRequest` interface, the
``mysubscriber`` function will be called with an *event* object.
@@ -83,8 +89,18 @@ As you can see, a subscription is made in terms of an
be an object that possesses an interface. The interface itself
provides documentation of what attributes of the event are available.
-For example, if you create event listener functions in a
-``subscribers.py`` file in your application like so:
+The return value of a subscriber function is ignored. Subscribers to
+the same event type are not guaranteed to be called in any particular
+order relative to each another.
+
+All the concrete :mod:`repoze.bfg` event types are documented in the
+:ref:`events_module` API documentation.
+
+An Example
+----------
+
+If you create event listener functions in a ``subscribers.py`` file in
+your application like so:
.. code-block:: python
:linenos:
@@ -96,8 +112,8 @@ For example, if you create event listener functions in a
print 'response', event.response
You may configure these functions to be called at the appropriate
-times by adding the following to your application's ``configure.zcml``
-file:
+times by adding the following ZCML to your application's
+``configure.zcml`` file:
.. code-block:: xml
:linenos:
@@ -112,10 +128,9 @@ file:
handler=".subscribers.handle_new_response"
/>
-See also :ref:`subscriber_directive`.
-
-The :meth:`repoze.bfg.configuration.Configurator.add_subscriber`
-method can be used to perform the same job:
+If you're not using ZCML, the
+:meth:`repoze.bfg.configuration.Configurator.add_subscriber` method
+can alternately be used to perform the same job:
.. ignore-next-block
.. code-block:: python
@@ -127,36 +142,27 @@ method can be used to perform the same job:
from subscribers import handle_new_request
from subscribers import handle_new_response
+ # "config" below is assumed to be an instance of a
+ # repoze.bfg.configuration.Configurator object
+
config.add_subscriber(handle_new_request, INewRequest)
config.add_subscriber(handle_new_response, INewResponse)
-This causes the functions as to be registered as event subscribers
-within the :term:`application registry` . Under this configuration,
-when the application is run, each time a new request or response is
-detected, a message will be printed to the console.
-
-.. sidebar:: The ``INewResponse`` Event vs. Middleware
-
- Postprocessing a response is usually better handled in a WSGI
- :term:`middleware` component than in subscriber code that is called
- by a :class:`repoze.bfg.interfaces.INewResponse` event. The
- :class:`repoze.bfg.interfaces.INewResponse` event exists almost
- purely for symmetry with the
- :class:`repoze.bfg.interfaces.INewRequest` event.
-
-We know that :class:`repoze.bfg.interfaces.INewRequest` events have a
-``request`` attribute, which is a :term:`WebOb` request, because the
-interface defined at :class:`repoze.bfg.interfaces.INewRequest` says
-it must. Likewise, we know that
-:class:`repoze.bfg.interfaces.INewResponse` events have a ``response``
-attribute, which is a response object constructed by your application,
-because the interface defined at
-:class:`repoze.bfg.interfaces.INewResponse` says it must.
+Either mechanism causes the functions in ``subscribers.py`` to be
+registered as event subscribers. Under this configuration, when the
+application is run, each time a new request or response is detected, a
+message will be printed to the console.
-The return value of a subscriber function is ignored. Subscribers to
-the same event type are not guaranteed to be called in any particular
-order relative to one another.
+Each of our subscriber functions accepts an ``event`` object and
+prints an attribute of the event object. This begs the question: how
+can we know which attributes a particular event has?
-All other concrete event types are documented in the
-:ref:`events_module` API documentation.
+We know that :class:`repoze.bfg.interfaces.INewRequest` event objects
+have a ``request`` attribute, which is a :term:`request` object,
+because the interface defined at
+:class:`repoze.bfg.interfaces.INewRequest` says it must. Likewise, we
+know that :class:`repoze.bfg.interfaces.INewResponse` events have a
+``response`` attribute, which is a response object constructed by your
+application, because the interface defined at
+:class:`repoze.bfg.interfaces.INewResponse` says it must.
diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst
index 0b2d5a876..67f111bb4 100644
--- a/docs/narr/extending.rst
+++ b/docs/narr/extending.rst
@@ -4,11 +4,10 @@ Extending An Existing :mod:`repoze.bfg` Application
===================================================
If the developer of a :mod:`repoze.bfg` application has obeyed certain
-constraints while building the application, a third party should be able to
-change the behavior of that application without needing to modify the actual
-source code that makes up the application. The behavior of a
-:mod:`repoze.bfg` application that obeys these constraints can be
-*overridden* or *extended* without modification.
+constraints while building that application, a third party should be
+able to change its behavior without needing to modify its source code.
+The behavior of a :mod:`repoze.bfg` application that obeys certain
+constraints can be *overridden* or *extended* without modification.
.. index::
triple: building; extensible; application
@@ -25,13 +24,13 @@ the a :term:`scan`, and you mustn't configure your :mod:`repoze.bfg`
application *imperatively* by using any code which configures the
application through methods of the :term:`Configurator` (except for
the :meth:`repoze.bfg.configuration.Configurator.load_zcml` method).
-Instead, should must use :term:`ZCML` for the equivalent
+
+Instead, must always use :term:`ZCML` for the equivalent
purposes. :term:`ZCML` declarations that belong to an application can
be "overridden" by integrators as necessary, but decorators and
-imperative code which perform the same tasks cannot.
-
-In general: use only :term:`ZCML` to configure your application if
-you'd like it to be extensible.
+imperative code which perform the same tasks cannot. Use only
+:term:`ZCML` to configure your application if you'd like it to be
+extensible.
Fundamental Plugpoints
~~~~~~~~~~~~~~~~~~~~~~
@@ -50,16 +49,16 @@ and templates.
ZCML Granularity
~~~~~~~~~~~~~~~~
-It's also extremely helpful to third party application "extenders"
-(aka "integrators") if the :term:`ZCML` that composes the
-configuration for an application is broken up into separate files
-which do very specific things. These more specific ZCML files can be
-reintegrated within the application's main ``configure.zcml`` via
-``<include file="otherfile.zcml"/>`` declarations. When ZCML files
-contain sets of specific declarations, an integrator can avoid
-including any ZCML he does not want by including only ZCML files which
-contain the declarations he needs. He is not forced to "accept
-everything" or "use nothing".
+It's extremely helpful to third party application "extenders" (aka
+"integrators") if the :term:`ZCML` that composes the configuration for
+an application is broken up into separate files which do very specific
+things. These more specific ZCML files can be reintegrated within the
+application's main ``configure.zcml`` via ``<include
+file="otherfile.zcml"/>`` declarations. When ZCML files contain sets
+of specific declarations, an integrator can avoid including any ZCML
+he does not want by including only ZCML files which contain the
+declarations he needs. He is not forced to "accept everything" or
+"use nothing".
For example, it's often useful to put all ``<route>`` declarations in
a separate ZCML file, as ``<route>`` statements have a relative
@@ -102,17 +101,19 @@ configuration imperatively, one of two things may be true:
*may* need to change the source code of the original application.
If the only source of trouble is the existence of
- :class:`repoze.bfg.view.bfg_view` decorators, you can just omit the
- ``<scan>`` directive in the application ZCML. This will cause the
- decorators to do nothing. At this point, you will need to convert
- all the configuration done in decorators into equivalent
- :term:`ZCML` and add that ZCML to an a separate Python package as
- described in :ref:`extending_the_application`.
-
- If the source of trouble is configuration done imperatively (perhaps
- in the function called during application startup), you'll need to
- change the code: convert imperative configuration into equivalent
- :term:`ZCML` declarations.
+ :class:`repoze.bfg.view.bfg_view` decorators, you can just prevent a
+ :term:`scan` from happening (by omitting the ``<scan>`` declaration
+ from ZCML or omitting any call to the
+ :meth:`repoze.bfg.configuration.Configurator.scan` method). This
+ will cause the decorators to do nothing. At this point, you will
+ need to convert all the configuration done in decorators into
+ equivalent :term:`ZCML` and add that ZCML to an a separate Python
+ package as described in :ref:`extending_the_application`.
+
+ If the source of trouble is configuration done imperatively in a
+ function called during application startup, you'll need to change
+ the code: convert imperative configuration statements into
+ equivalent :term:`ZCML` declarations.
Once this is done, you should be able to extend or override the
application like any other (see :ref:`extending_the_application`).
@@ -157,7 +158,8 @@ something like this:
- Create an ``overrides.zcml`` file within the new package. The
statements in the ``overrides.zcml`` file will override any ZCML
- statements made within the original application (such as views).
+ statements made within the original application (such as view
+ declarations).
- Create Python files containing views and other overridden elements,
such as templates and static resources as necessary, and wire these
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
new file mode 100644
index 000000000..e533fdcb2
--- /dev/null
+++ b/docs/narr/firstapp.rst
@@ -0,0 +1,767 @@
+.. _configuration_narr:
+
+Creating Your First :mod:`repoze.bfg` Application
+=================================================
+
+We'll walk through the creation of a tiny :mod:`repoze.bfg`
+application in this chapter, and explain in more detail how the
+application works.
+
+.. index::
+ single: helloworld
+
+.. _helloworld_imperative:
+
+Hello World, Goodbye World (Imperative)
+---------------------------------------
+
+Here's one of the simplest :mod:`repoze.bfg` applications, configured
+imperatively:
+
+.. code-block:: python
+ :linenos:
+
+ from webob import Response
+ from paste.httpserver import serve
+ from repoze.bfg.configuration import Configurator
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+When this code is inserted into a Python script named
+``helloworld.py`` and executed by a Python interpreter which has the
+:mod:`repoze.bfg` software installed, an HTTP server is started on
+port 8080. When port 8080 is visited by a user agent on the root URL
+(``/``), the server will simply serve up the text "Hello world!" with
+the HTTP response values ``200 OK`` as a response code and a
+``Content-Type`` header value of ``text/plain``. But for reasons
+we'll better understand shortly, when visited by a user agent on the
+URL ``/goodbye``, the server will serve up "Goodbye world!".
+
+Let's examine this program piece-by-piece.
+
+Imports
+~~~~~~~
+
+The above script defines the following set of imports:
+
+.. code-block:: python
+ :linenos:
+
+ from webob import Response
+ from paste.httpserver import serve
+ from repoze.bfg.configuration import Configurator
+
+:mod:`repoze.bfg` uses the :term:`WebOb` library as the basis for its
+:term:`request` and :term:`response` objects. The script uses the
+:class:`webob.Response` class later in the script to create a
+:term:`response` object.
+
+Like many other Python web frameworks, :mod:`repoze.bfg` uses the
+:term:`WSGI` protocol to connect an application and a web server
+together. The :mod:`paste.httpserver` server is used in this example
+as a WSGI server for convenience, as ``Paste`` is a dependency of
+:mod:`repoze.bfg` itself. However, :mod:`repoze.bfg` applications can
+be served by any WSGI server.
+
+The script also imports the ``Configurator`` class from the
+``repoze.bfg.configuration`` module. This class is used to configure
+:mod:`repoze.bfg` for a particular application. An instance of this
+class provides methods which help configure various parts of
+:mod:`repoze.bfg` for a given application deployment.
+
+View Declaration
+~~~~~~~~~~~~~~~~
+
+The above script, beneath its set of imports, defines two functions:
+one named ``hello_world`` and one named ``goodbye_world``.
+
+.. code-block:: python
+ :linenos:
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+Each function accepts a single argument (``request``) and returns an
+instance of the :class:`webob.Response` class. In the ``hello_world``
+function, the string ``'Hello world!'`` is passed to the ``Response``
+constructor as the *body* of the response. In the ``goodbye_world``
+function, the string ``'Goodbye world!'`` is passed.
+
+Each of these functions is known as a :term:`view callable`. View
+callables in a "real" :mod:`repoze.bfg` application are often
+functions which accept a :term:`request` and return a
+:term:`response`. A view callable can be represented via another type
+of object, like a class or an instance, but for our purposes here, a
+function serves us well.
+
+A view callable is called with a :term:`request` object, which is a
+representation of an HTTP request sent by a remote user agent. A view
+callable is required to return a :term:`response` object because a
+response object has all the information necessary to formulate an
+actual HTTP response; this object is then converted to text and sent
+back to the requesting user agent.
+
+The ``hello_world`` view callable defined by the script does nothing
+but return a response with the body ``Hello world!``; the
+``goodbye_world`` view callable returns a response with the body
+``Goodbye world!``.
+
+.. index::
+ pair: traversal; introduction
+
+.. _traversal_intro:
+
+An Introduction to Traversal
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you've run the code listed in :ref`helloworld_imperative` already,
+you've unwittingly configured :mod:`repoze.bfg` to serve an
+application that relies on :term:`traversal`. A full explanation of
+how :mod:`repoze.bfg` locates "the right" :term:`view callable` for a
+given request requires some explanation of :term:`traversal`.
+
+Traversal is part of a mechanism used by :mod:`repoze.bfg` to map the
+URL of some request to a particular :term:`view callable`. It is not
+the only mechanism made available by :mod:`repoze.bfg` that allows the
+mapping a URL to a view callable. Another distinct mode known as
+:term:`URL dispatch` can alternately be used to find a view callable
+based on a URL. However, our sample application uses only
+:term:`traversal`.
+
+In :mod:`repoze.bfg` terms, :term:`traversal` is the act of walking
+over an object graph starting from a :term:`root` object in order to
+find a :term:`context` object and a :term:`view name`. Once a context
+and a view name are found, these two bits of information, plus other
+information from the request are used to look up a :term:`view
+callable`. :mod:`repoze.bfg` bothers to do traversal only because the
+information returned from traversal allows a view callable to be
+found.
+
+The individual path segments of the "path info" portion of a URL (the
+data following the hostname and port number, but before any query
+string elements or fragments, for example the ``/a/b/c`` portion of
+the URL ``http://example.com/a/b/c?foo=1``) are used as "steps" during
+traversal.
+
+.. note:: A useful analogy of how :mod:`repoze.bfg` :term:`traversal`
+ works is available within the chapter section entitled
+ :ref:`traversal_behavior`.
+
+The results of a :term:`traversal` include a :term:`context` and a
+:term:`view name`. The :term:`view name` is the *first* URL path
+segment in the set of path segments "left over" in the results of
+:term:`traversal`. This will either be the empty string (``''``) or a
+non-empty string (one of the path segment strings). The empty string
+represents the :term:`default view` of a context object.
+
+The :term:`default view` is found when all path elements in the URL
+are exhausted before :term:`traversal` returns a :term:`context`
+object, causing the :term:`view name` to be ``''`` (the empty string).
+When no path segments are "left over" after traversal, the
+:term:`default view` for the context found is invoked.
+
+If traversal returns a non-empty :term:`view name`, it means that
+traversal "ran out" of nodes in the graph before it finished
+exhausting all the path segments implied by the path info of the URL:
+no segments are "left over". In this case, because the :term:`view
+name` is non-empty, a *non-default* view callable will be invoked.
+
+The combination of the :term:`context` object and the :term:`view
+name` (and, in more complex configurations, other :term:`predicate`
+values) are used to find "the right" :term:`view callable`, which will
+be invoked after traversal.
+
+The object graph of our hello world application is very simple:
+there's exactly one object in our graph; the default :term:`root`
+object.
+
+Relating Traversal to the Hello World Application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Our application's :term:`root` object is the *default* root object
+used when one isn't otherwise specified in application configuration.
+This root object does not have a ``__getitem__`` method, thus it has
+no children. Although in a more complex system there can be many
+contexts which URLs resolve to in our application, effectively there
+is only ever one context: the root object.
+
+We have only a single default view registered (the registration for
+the ``hello_world`` view callable). Due to this set of circumstances,
+you can consider the sole possible URL that will resolve to a
+:term:`default view` in this application the root URL ``'/'``. It is
+the only URL that will resolve to the :term:`view name` of ``''`` (the
+empty string).
+
+We have only a single view registered for the :term:`view name`
+``goodbye`` (the registration for the ``goodbye_world`` view
+callable). Due to this set of circumstances, you can consider the
+sole possible URL that will resolve to the ``goodbye_world`` in this
+application the URL ``'/goodbye'`` because it is the only URL that
+will resolve to the :term:`view name` of ``goodbye``.
+
+.. index::
+ pair: imperative; configuration
+ single: Configurator
+
+.. _helloworld_imperative_appconfig:
+
+Application Configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the above script, the following code, representing the
+*configuration* of an application which uses the previously defined
+imports and function definitions is placed within the confines of an
+``if`` statement:
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+Let's break this down this piece-by-piece.
+
+Configurator Construction
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+
+The ``if __name__ == '__main__':`` line above represents a Python
+idiom: the code inside this if clause is not invoked unless the script
+is run directly from the command line via, for example, ``python
+helloworld.py`` where the file named ``helloworld.py`` contains the
+entire script body. ``helloworld.py`` in this case is a Python
+*module*. Using the ``if`` clause is necessary (or at least "best
+practice") because code in any Python module may be imported by
+another Python module. By using this idiom, the script is indicating
+that it does not want the code within the ``if`` statement to execute
+if this module is imported; the code within the ``if`` block should
+only be run during a direct script execution.
+
+The ``config = Configurator()`` line above creates an instance of the
+:class:`repoze.bfg.configuration.Configurator` class. The resulting
+``config`` object represents an API which the script uses to configure
+this particular :mod:`repoze.bfg` application.
+
+.. note::
+
+ An instance of the :class:`repoze.bfg.configuration.Configurator`
+ class is a *wrapper* object which mutates an :term:`application
+ registry` as its methods are called. An application registry
+ represents the configuration state of a :mod:`repoze.bfg`
+ application. The ``Configurator`` is not itself an
+ :term:`application registry`, it is a mechanism used to configure
+ an application registry. The underlying application registry
+ object being configured by a ``Configurator`` is available as its
+ ``registry`` attribute.
+
+Beginning Configuration
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+
+ config.begin()
+
+The :meth:`repoze.bfg.configuration.Configurator.begin` method tells
+the the system that application configuration has begun. In
+particular, this causes the :term:`application registry` associated
+with this configurator to become the "current" application registry,
+meaning that code which attempts to use the application registry
+:term:`thread local` will obtain the registry associated with the
+configurator. This is an explicit step because it's sometimes
+convenient to use a configurator without causing the registry
+associated with the configurator to become "current".
+
+Adding Configuration
+~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+ :linenos:
+
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+
+Each of these lines calls the
+:meth:`repoze.bfg.configuration.Configurator.add_view` method. The
+``add_view`` method of a configurator registers a :term:`view
+configuration` within the :term:`application registry`. A :term:`view
+configuration` represents a :term:`view callable` which must be
+invoked when a set of circumstances related to the :term:`request` is
+true. This "set of circumstances" is provided as one or more keyword
+arguments to the ``add_view`` method, otherwise known as
+:term:`predicate` arguments.
+
+The line ``config.add_view(hello_world)`` registers the
+``hello_world`` function as a view callable. The ``add_view`` method
+of a Configurator must be called with a view callable object as its
+first argument, so the first argument passed is ``hello_world``
+function we'd like to use as a :term:`view callable`. However, this
+line calls ``add_view`` with a single default :term:`predicate`
+argument, the ``name`` predicate with a value of ``''``, meaning that
+we'd like :mod:`repoze.bfg` to invoke the ``hello_world`` view
+callable for any request for the :term:`default view` of an object.
+
+Our ``hello_world`` :term:`view callable` returns a Response instance
+with a body of ``Hello world!`` in the configuration implied by this
+script. It is configured as a :term:`default view`. Therefore, a
+user agent contacting a server running this application will receive
+the greeting ``Hello world!`` when any :term:`default view` is
+invoked.
+
+.. sidebar:: View Dispatch and Ordering
+
+ When :term:`traversal` is used, :mod:`repoze.bfg` chooses the most
+ specific view callable based *only* on view :term:`predicate`
+ applicability. This is unlike :term:`URL dispatch`, another
+ dispatch mode of :mod:`repoze.bfg` (and other frameworks, like
+ :term:`Pylons` and :term:`Django`) which first uses an ordered
+ routing lookup to resolve the request to a view callable by running
+ it through a relatively-ordered series of URL path matches. We're
+ not really concerned about the finer details of :term:`URL
+ dispatch` right now. It's just useful to use for demonstrative
+ purposes: the ordering of calls to
+ :meth:`repoze.bfg.configuration.Configurator.add_view`` is never
+ very important. We can register ``goodbye_world`` first and
+ ``hello_world`` second; :mod:`repoze.bfg` will still give us the
+ most specific callable when a request is dispatched to it.
+
+The line ``config.add_view(goodbye_world, name='goodbye')`` registers
+the ``goodbye_world`` function as a view callable. The line calls
+``add_view`` with the view callable as the first required positional
+argument, and a :term:`predicate` keyword argument ``name`` with the
+value ``'goodbye'``. This :term:`view configuration` implies that a
+request with a :term:`view name` of ``goodbye`` should cause the
+``goodbye_world`` view callable to be invoked. For the purposes of
+this discussion, the :term:`view name` can be considered the first
+non-empty path segment in the URL: in particular, this view
+configuration will match when the URL is ``/goodbye``.
+
+Our ``goodbye_world`` :term:`view callable` returns a Response
+instance with a body of ``Goodbye world!`` in the configuration
+implied by this script. It is configured as with a :term:`view name`
+predicate of ``goodbye``. Therefore, a user agent contacting a server
+running this application will receive the greeting ``Goodbye world!``
+when the path info part of the request is ``/goodbye``.
+
+Each invocation of the ``add_view`` method implies a :term:`view
+configuration` registration. Each :term:`predicate` provided as a
+keyword argument to the ``add_view`` method narrows the set of
+circumstances which would cause the view configuration's callable to
+be invoked. In general, a greater number of predicates supplied along
+with a view configuration will more strictly limit the applicability
+of its associated view callable. When :mod:`repoze.bfg` processes a
+request, however, the view callable with the *most specific* view
+configuration (the view configuration that matches the largest number
+of predicates) is always invoked.
+
+Earlier we explained that the server would return ``Hello world!`` if
+you visited the *root* (``/``) URL. However, actually, because the
+view configuration registration for the ``hello_world`` view callable
+has no :term:`predicate` arguments, the ``hello_world`` view callable
+is applicable for the :term:`default view` of any :term:`context`
+resulting from a request. This isn't all that interesting in this
+application, because we always only have *one* potential context (the
+root object): it is the only object in the graph.
+
+We've also registered a view configuration for another circumstance:
+the ``goodbye_world`` view callable has a ``name`` predicate of
+``goodbye``, meaning that it will match for requests that have the
+:term:`view name` ``goodbye`` unlike the ``hello_world`` view
+configuration registration, which will only match the default view
+(view name ``''``) of a request. Because :mod:`repoze.bfg` chooses
+the best view configuration for any request, the ``goodbye_world``
+view callable will be used when the URL contains path information that
+ends with ``/goodbye``.
+
+Ending Configuration
+~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+
+ config.end()
+
+The :meth:`repoze.bfg.configuration.Configurator.end` method tells the
+the system that application configuration has ended. It is the
+inverse of :meth:`repoze.bfg.configuration.Configurator.begin`. In
+particular, this causes the :term:`application registry` associated
+with this configurator to no longer be the "current" application
+registry, meaning that code which attempts to use the application
+registry :term:`thread local` will no longer obtain the registry
+associated with the configurator.
+
+.. index::
+ single: make_wsgi_app
+ pair: WSGI; application
+ triple: WSGI; application; creation
+
+WSGI Application Creation
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+
+ app = config.make_wsgi_app()
+
+After configuring views and ending configuration, the script creates a
+WSGI *application* via the
+:meth:`repoze.bfg.configuration.Configurator.make_wsgi_app` method. A
+call to ``make_wsgi_app`` implies that all configuration is finished
+(meaning all method calls to the configurator which set up views, and
+various other configuration settings have been performed). The
+``make_wsgi_app`` method returns a :term:`WSGI` application object
+that can be used by any WSGI server to present an application to a
+requestor.
+
+The :mod:`repoze.bfg` application object, in particular, is an
+instance of the :class:`repoze.bfg.router.Router` class. It has a
+reference to the :term:`application registry` which resulted from
+method calls to the configurator used to configure it. The Router
+consults the registry to obey the policy choices made by a single
+application. These policy choices were informed by method calls to
+the ``Configurator`` made earlier; in our case, the only policy
+choices made were implied by two calls to the ``add_view`` method,
+telling our application that it should effectively serve up the
+``hello_world`` view callable to any user agent when it visits the
+root URL, and the ``goodbye_world`` view callable to any user agent
+when it visits the URL with the path info ``/goodbye``.
+
+WSGI Application Serving
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. ignore-next-block
+.. code-block:: python
+
+ serve(app)
+
+Finally, we actually serve the application to requestors by starting
+up a WSGI server. We happen to use the :func:`paste.httpserver.serve`
+WSGI server runner, using the default TCP port of 8080, and we pass it
+the ``app`` object (an instance of the
+:class:`repoze.bfg.router.Router` class) as the application we wish to
+serve. This causes the server to start listening on the TCP port. It
+will serve requests forever, or at least until we stop it by killing
+the process which runs it.
+
+Conclusion
+~~~~~~~~~~
+
+Our hello world application is one of the simplest possible
+:mod:`repoze.bfg` applications, configured "imperatively". We can see
+a good deal of what's going on "under the hood" when we configure a
+:mod:`repoze.bfg` application imperatively. However, another mode of
+configuration exists named *declarative* configuration.
+
+.. index::
+ pair: helloworld; declarative
+ single: helloworld
+
+.. _helloworld_declarative:
+
+Hello World, Goodbye World (Declarative)
+----------------------------------------
+
+:mod:`repoze.bfg` can be configured for the same "hello world"
+application "declaratively", if so desired, as described in
+:ref:`declarative_configuration`.
+
+Create a file named ``helloworld.py``:
+
+.. code-block:: python
+ :linenos:
+
+ from webob import Response
+ from paste.httpserver import serve
+ from repoze.bfg.configuration import Configurator
+
+ def hello_world(request):
+ return Response('Hello world!')
+
+ def goodbye_world(request):
+ return Response('Goodbye world!')
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.load_zcml('configure.zcml')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+Create a file named ``configure.zcml`` in the same directory as the
+previously created ``helloworld.py``:
+
+.. code-block:: xml
+ :linenos:
+
+ <configure xmlns="http://namespaces.repoze.org/bfg">
+
+ <include package="repoze.bfg.includes" />
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+ </configure>
+
+This pair of files forms an application functionally equivalent to the
+application we created earlier. Let's examine the differences between
+the code described in :ref:`helloworld_imperative` and the code above.
+
+In :ref:`helloworld_imperative_appconfig`, we had the following lines
+within the ``if __name__ == '__main__'`` section of ``helloworld.py``:
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+In our "declarative" code, we've added a call to the
+:meth:`repoze.bfg.configuration.Configurator.load_zcml` method with
+the value ``configure.zcml``, and we've removed the lines which read
+``config.add_view(hello_world)`` and ``config.add_view(goodbye_world,
+name='goodbye')``, so that it now reads as:
+
+.. code-block:: python
+ :linenos:
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.begin()
+ config.load_zcml('configure.zcml')
+ config.end()
+ app = config.make_wsgi_app()
+ serve(app)
+
+Everything else is much the same.
+
+The ``config.load_zcml('configure.zcml')`` line tells the configurator
+to load configuration declarations from the ``configure.zcml`` file
+which sits next to ``helloworld.py``. Let's take a look at the
+``configure.zcml`` file now:
+
+.. code-block:: xml
+ :linenos:
+
+ <configure xmlns="http://namespaces.repoze.org/bfg">
+
+ <include package="repoze.bfg.includes" />
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+ </configure>
+
+Let's deconstruct what this actually does.
+
+The ``<configure>`` Tag
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``configure.zcml`` ZCML file contains this bit of XML:
+
+.. code-block:: xml
+ :linenos:
+
+ <configure xmlns="http://namespaces.repoze.org/bfg">
+
+ <!-- other directives -->
+
+ </configure>
+
+Because :term:`ZCML` is XML, and because XML requires a single root
+tag for each document, every ZCML file used by :mod:`repoze.bfg` must
+contain a ``configure`` container directive, which acts as the root
+XML tag. It is a "container" directive because its only job is to
+contain other directives.
+
+See also :ref:`configure_directive` and :ref:`word_on_xml_namespaces`.
+
+The ``<include>`` Tag
+~~~~~~~~~~~~~~~~~~~~~
+
+The ``configure.zcml`` ZCML file contains this bit of XML within the
+``<configure>`` root tag:
+
+.. code-block:: xml
+
+ <include package="repoze.bfg.includes" />
+
+This singleton (self-closing) tag instructs ZCML to load a ZCML file
+from the Python package with the :term:`dotted Python name`
+:mod:`repoze.bfg.includes`, as specified by its ``package`` attribute.
+This particular ``<include>`` declaration is required because it
+actually allows subsequent declaration tags (such as ``<view>``, which
+we'll see shortly) to be recognized. The ``<include>`` tag
+effectively just includes another ZCML file; this causes its
+declarations to be executed. In this case, we want to load the
+declarations from the file named ``configure.zcml`` within the
+:mod:`repoze.bfg.includes` Python package. We know we want to load
+the ``configure.zcml`` from this package because ``configure.zcml`` is
+the default value for another attribute of the ``<include>`` tag named
+``file``. We could have spelled the include tag more verbosely, but
+equivalently as:
+
+.. code-block:: xml
+ :linenos:
+
+ <include package="repoze.bfg.includes"
+ file="configure.zcml"/>
+
+The ``<include>`` tag that includes the ZCML statements implied by the
+``configure.zcml`` file from the Python package named
+:mod:`repoze.bfg.includes` is basically required to come before any
+other named declaration in an application's ``configure.zcml``. If it
+is not included, subsequent declaration tags will fail to be
+recognized, and the configuration system will generate a traceback.
+However, the ``<include package="repoze.bfg.includes"/>`` tag needs to
+exist only in a "top-level" ZCML file, it needn't also exist in ZCML
+files *included by* a top-level ZCML file.
+
+See also :ref:`include_directive`.
+
+The ``<view>`` Tag
+~~~~~~~~~~~~~~~~~~
+
+The ``configure.zcml`` ZCML file contains these bits of XML *after* the
+``<include>`` tag, but *within* the ``<configure>`` root tag:
+
+.. code-block:: xml
+ :linenos:
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+These ``<view>`` declaration tags direct :mod:`repoze.bfg` to create
+two :term:`view configuration` registrations. The first ``<view>``
+tag has an attribute (the attribute is also named ``view``), which
+points at a :term:`dotted Python name`, referencing the
+``hello_world`` function defined within the ``helloworld`` package.
+The second ``<view>`` tag has a ``view`` attribute which points at a
+:term:`dotted Python name`, referencing the ``goodbye_world`` function
+defined within the ``helloworld`` package. The second ``<view>`` tag
+also has an attribute called ``name`` with a value of ``goodbye``.
+
+These effect of the ``<view>`` tag declarations we've put into our
+``configure.zcml`` is functionally equivalent to the effect of lines
+we've already seen in an imperatively-configured application. We're
+just spelling things differently, using XML instead of Python.
+
+In our previously defined application, in which we added view
+configurations imperatively, we saw this code:
+
+.. ignore-next-block
+.. code-block:: python
+ :linenos:
+
+ config.add_view(hello_world)
+ config.add_view(goodbye_world, name='goodbye')
+
+Each ``<view>`` declaration tag encountered in a ZCML file effectively
+invokes the :meth:`repoze.bfg.configuration.Configurator.add_view`
+method on the behalf of the developer. Various attributes can be
+specified on the ``<view>`` tag which influence the :term:`view
+configuration` it creates.
+
+Since the relative ordering of calls to
+:meth:`repoze.bfg.configuration.Configurator.add_view` doesn't matter
+(see the sidebar entitled *View Dispatch and Ordering*, the relative
+order of ``<view>`` tags in ZCML doesn't matter either. The following
+ZCML orderings are completely equivalent:
+
+.. topic:: Hello Before Goodbye
+
+ .. code-block:: xml
+ :linenos:
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+.. topic:: Goodbye Before Hello
+
+ .. code-block:: xml
+ :linenos:
+
+ <view
+ name="goodbye"
+ view="helloworld.goodbye_world"
+ />
+
+ <view
+ view="helloworld.hello_world"
+ />
+
+We've now configured a :mod:`repoze.bfg` helloworld application
+declaratively.
+
+References
+----------
+
+For more information about the API of a ``Configurator`` object, see
+:class:`repoze.bfg.configuration.Configurator` . The equivalent ZCML
+declaration tags are introduced in narrative documentation chapters as
+necessary.
+
+For more information about :term:`traversal`, see
+:ref:`traversal_chapter`.
+
+For more information about :term:`view configuration`, see
+:ref:`views_chapter`.
+
diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst
index 4faccae45..f94df725a 100644
--- a/docs/narr/hybrid.rst
+++ b/docs/narr/hybrid.rst
@@ -5,47 +5,32 @@ Combining Traversal and URL Dispatch
:mod:`repoze.bfg` makes an honest attempt to unify the (largely
incompatible) concepts of :term:`traversal` and :term:`url dispatch`.
+
When you write *most* :mod:`repoze.bfg` applications, you'll be using
either one or the other concept, but not both, to resolve URLs to
-:term:`view` callables.
-
-However, for some problems, it's useful to use both traversal *and*
-URL dispatch within the same application. :mod:`repoze.bfg` makes
-this possible.
-
-Reasoning about a "hybrid" URL dispatch + traversal model can be
-difficult because the combination of the two concepts seems to fall
-outside the sweet spot of `the magical number seven plus or minus 2
-<http://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two>`_.
-To reason successfully about using URL dispatch and traversal
-together, you need to understand 1) URL pattern matching, 2) root
-factories and 3) the traversal algorithm, and the interactions between
-all of them. Therefore, use of this pattern is not recommended unless
-you *really* need to use it.
+:term:`view` callables. However, for some problems, it's useful to
+use both traversal *and* URL dispatch within the same application.
+:mod:`repoze.bfg` makes this possible via *hybrid* applications.
.. warning:: Creating applications that use hybrid-mode features of
:mod:`repoze.bfg` is a advanced topic that exposes non-trivial
- corner cases; you may need to understand more deeply how
- :mod:`repoze.bfg` works to understand the concepts discussed in
- this chapter. To that end, it's useful to read
- :ref:`router_chapter` to get a more holistic understanding of
- what's happening "under the hood" to use this feature.
+ corner cases. Don't use it unless you must.
The Schism
----------
-BFG, when used according to the currently published tutorials in its
-documentation is sort of a dual-mode framework. The tutorials explain
-how to create an application terms of using either :term:`url
-dispatch` *or* :term:`traversal`. It's useful to examine that pattern
-in order to understand the schism between the two.
+:mod:`repoze.bfg`, especially when used according to the tutorials in
+its documentation is sort of a "dual-mode" framework. The tutorials
+explain how to create an application in terms of using either
+:term:`url dispatch` *or* :term:`traversal`. But not both. It's
+useful to examine that pattern in order to understand the schism
+between the two.
URL Dispatch Only
~~~~~~~~~~~~~~~~~
An application that uses :term:`url dispatch` exclusively to map URLs
-to code will usually exclusively have declarations like this within
-their ``configure.zcml`` file:
+to code will often have declarations like this within :term:`ZCML`:
.. code-block:: xml
@@ -61,25 +46,38 @@ their ``configure.zcml`` file:
view=".views.bazbuz"
/>
-In other words, each route typically corresponds with a single view
-function, and when the route is matched during a request, the view
-attached to it is invoked. Typically, applications that use only URL
-dispatch won't have any ``<view>`` statements in the
-``configure.zcml``.
+In other words, each :term:`route` typically corresponds with a single
+view callable, and when that route is matched during a request, the
+view callable attached to it is invoked.
+
+"Under the hood", these ``<route>`` declarations register a view for
+each route. This view is registered for the following context/request
+type/name triad:
-"Under the hood", these ``<route>`` statements register a view for
-each route for the context :term:`interface` ``None`` (implying any
-context) and a route-statement-specific (dynamically-constructed)
-:term:`request type` using the empty string as the :term:`view name`
-(implying the default view). This ensures that the named view will
-only be called when the route it's attached to actually matches.
+- the context :term:`interface` ``None``, implying any context.
+
+- A :term:`request type` interface that inherits from
+ :class:`repoze.bfg.interfaces.IRequest` *and* a
+ dynamically-constructed route-statement-specific :term:`interface`.
+
+- the empty string as the :term:`view name`, implying the default
+ view.
+
+This usually ensures that the named view will only be called when the
+route it's attached to actually matches.
+
+Typically, applications that use only URL dispatch won't have any
+``<view>`` directives in ZCML and will not have any calls to
+:meth:`repoze.bfg.configuration.Configurator.add_view` in their
+startup code.
Traversal Only
~~~~~~~~~~~~~~
-In application that uses :term:`traversal` exclusively to map URLs to
-code just won't have any ``<route>`` declarations. Instead, its ZCML
-(or bfg_view decorators) will imply declarations that look like this:
+An application that uses :term:`traversal` exclusively to map URLs to
+code just won't have any ``<route>`` declarations or calls to the
+:meth:`repoze.bfg.configuration.Configurator.add_route`. Instead, its
+view configuration will imply declarations that look like this:
.. code-block:: xml
@@ -94,11 +92,18 @@ code just won't have any ``<route>`` declarations. Instead, its ZCML
/>
"Under the hood", the above view statements register a view using the
-:term:`context` interface ``None``, the
-:class:`repoze.bfg.interfaces.IRequest` :term:`request type` with a
-:term:`view name` matching the name= argument. The "foobar" view
-above will match the URL ``/a/b/c/foobar`` or ``/foobar``, etc,
-assuming that no view is named "a", "b", or "c" during traversal.
+following context/request/name triad:
+
+- The :term:`context` interface ``None``
+
+- the the :class:`repoze.bfg.interfaces.IRequest` :term:`request type`
+ interface
+
+- a :term:`view name` matching the ``name=`` argument.
+
+The ``.views.foobar`` view callable above will be called when the URL
+``/a/b/c/foobar`` or ``/foobar``, etc, assuming that no view is named
+``a``, ``b``, or ``c`` during traversal.
.. index::
single: hybrid mode application
@@ -106,12 +111,46 @@ assuming that no view is named "a", "b", or "c" during traversal.
Hybrid Applications
-------------------
-We've seen how the current crop of tutorials explain that you can use
-*either* traversal or url dispatch to create a :mod:`repoze.bfg`
-application. However, it is possible to combine the competing
-concepts of traversal and url dispatch to resolve URLs to code within
-the same application by using a ``<route>`` declaration that contains
-the special token ``*traverse`` in its path.
+So far we've seen that *either* traversal or url dispatch to create a
+:mod:`repoze.bfg` application. However, it is possible to combine the
+competing concepts of traversal and url dispatch to resolve URLs to
+code within the same application.
+
+Reasoning about a "hybrid" URL dispatch + traversal model can be
+difficult because the combination of the two concepts seems to fall
+outside the sweet spot of `the magical number seven plus or minus 2
+<http://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two>`_.
+To reason successfully about using URL dispatch and traversal
+together, you need to understand 1) URL pattern matching, 2) root
+factories and 3) the traversal algorithm, and the interactions between
+all of them. Therefore, use of this pattern is not recommended unless
+you *really* need to use it.
+
+Understanding how hybrid mode works requires a little "inside
+baseball" knowledge of how :mod:`repoze.bfg` works. No matter whether
+:term:`traversal` or :term:`URL dispatch` is used, :mod:`repoze.bfg`
+uses the :term:`Zope Component Architecture` under the hood to
+dispatch a request to a :term:`view callable`. In Zope Component
+Architecture-speak, a view callable is a "multi adapter" registered
+for a :term:`context` type and a :term:`request` type as well as a
+particular :term:`view name`, aka a "triad". When a request is
+generated and a :term:`router` performs its logic, it locates these
+three values. These three values are fed to the :term:`application
+registry` as a query to find "the best" view callable.
+
+.. note:: To understand this process more deeply, it may be useful to
+ read :ref:`router_chapter`.
+
+To "turn on" hybrid mode, use a :term:`route configuration` that
+includes a ``path`` argument that contains a special dynamic part:
+either ``*traverse`` or ``*subpath``.
+
+Using ``*traverse`` In a Route Path
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To create a hybrid application, combine traversal and URL dispatch by
+using a ``<route>`` declaration that contains the special token
+``*traverse`` in its path.
.. code-block:: xml
@@ -148,8 +187,7 @@ factory looks like so:
We've defined a bogus graph here that can be traversed, and a
root_factory method that returns the root of the graph. Because the
Traversable object we've defined has a ``__getitem__`` method that
-does something (sort of) useful (see :ref:`traversal_chapter` for more
-info about how traversal works), using traversal against the root
+does something nominally useful, using traversal against the root
implied by a route statement becomes a not-completely-insane thing to
do. So for this route:
@@ -169,10 +207,10 @@ requested by a user was ``http://example.com/one/two/a/b/c``, and the
above route was matched (some other route might match before this one
does), the traversal path used against the root would be ``a/b/c``.
:mod:`repoze.bfg` will attempt to traverse a graph through the edges
-"a", "b", and "c". In our above example, that would imply that the
-*context* of the view would be the ``Traversable`` object we've named
-"c" in our bogus graph, using the ``.views.home`` view as the view
-callable.
+``a``, ``b``, and ``c``. In our above example, that would imply that
+the *context* of the view would be the ``Traversable`` object we've
+named ``c`` in our bogus graph, using the ``.views.home`` view as the
+view callable.
We can also define extra views that match a route:
@@ -203,35 +241,12 @@ URL is "http://example.com/one/two/a/another", the ``.views.another``
view will be called.
.. index::
- pair: route; factory
- single: route factory
-
-Route Factories
----------------
-
-A "route" declaration can mention a "factory". When a factory is
-attached to a route, it is used to generate a root (it's a :term:`root
-factory`) instead of the *default* root factory.
-
-.. code-block:: xml
-
- <route
- factory=".models.root_factory"
- path="/abc/*traverse"
- name="abc"
- />
-
-In this way, each route can use a different factory, making it
-possible to traverse different graphs based on some routing parameter
-within the same application.
-
-.. index::
pair: subpath; route
.. _star_subpath:
Using ``*subpath`` in a Route Path
-----------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are certain (extremely rare) cases when you'd like to influence
the traversal :term:`subpath` when a route matches without actually
@@ -312,23 +327,13 @@ view name to be "bazbuz", the ``.views.bazbuz`` view will be used.
However, if the "abc" route matches, and traversal finds the view name
to be "bazbuz", the ``.views.bazbuz2`` view will be used.
-``context`` Type (aka "for") Registrations Bind More Tightly Than ``request`` Type Registrations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``context`` Type Registrations Bind More Tightly Than ``request`` Type Registrations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This corner case is only interesting if you are using a hybrid
application and you believe the "wrong" view is being called for a
given request.
-This explanation requires a little "inside baseball" knowledge of how
-:mod:`repoze.bfg` works. :mod:`repoze.bfg` uses the :term:`Zope
-Component Architecture` under the hood to dispatch a request to a
-:term:`view`. In Zope Component Architecture-speak, a view is a
-"multi adapter" registered for a :term:`context` type and a
-:term:`request` type as well as a particular :term:`view name`. When
-a request is generated and a context is found by the :mod:`repoze.bfg`
-:term:`router`, it uses these two values, along with the :term:`view
-name` to try to locate a view callable.
-
A view is registered for a ``route`` either as its default view via
the ``view=`` attribute of a ``route`` declaration in ZCML *or* as a
standalone ``<view>`` declaration (or via the ``@bfg_route``
@@ -464,15 +469,3 @@ you must the special ``*traverse`` token to the route's "path"., e.g.:
route_name="abc"
/>
-.. index::
- pair: route; ordering
-
-Route Ordering
-~~~~~~~~~~~~~~
-
-One other thing to look out for: ``<route>`` statements need to be
-ordered relative to each other; view statements don't. ``<route>``
-statement ordering is very important, because routes are evaluated in
-a specific order, unlike traversal, which depends on emergent behavior
-rather than an ordered list of directives.
-
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index d0bb7ae92..1028e7d72 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -10,31 +10,24 @@ Before You Install
------------------
You will need `Python <http://python.org>`_ version 2.4 or better to
-run :mod:`repoze.bfg`. It has been tested under Python 2.4.6, Python
-2.5.4 and Python 2.6.2, and Python 2.7a1. Development of
-:mod:`repoze.bfg` is currently done primarily under Python 2.4 and
-Python 2.5. :mod:`repoze.bfg` does *not* run under any version of
-Python before 2.4, and does *not* run under Python 3.X.
-
-.. sidebar:: You Don't Need A Compiler
-
- Installation of :mod:`repoze.bfg` does not require the compilation
- of any C code, so as long as you have a Python interpreter that
- meets the requirements mentioned, you do not need to have
- development tools installed on the target machine to install
- :mod:`repoze.bfg`.
-
-:mod:`repoze.bfg` is known to run properly on all popular Unix-like
-systems such as Linux, MacOS X, and FreeBSD. :mod:`repoze.bfg` is
-also known to run on Windows systems. However, none of its developers
-use Windows as a primary development platform. It is also known to
-run on Google's App Engine.
-
-.. note:: If you'd like to help us make sure :mod:`repoze.bfg` runs on
- your favorite alternate platform, we'd love to hear from you.
- Please contact us via the `repoze.dev maillist
- <http://lists.repoze.org/listinfo/repoze-dev>`_ if you'd like to
- contribute.
+run :mod:`repoze.bfg`.
+
+.. sidebar:: Python Versions
+
+ :mod:`repoze.bfg` has been tested under Python 2.4.6, Python 2.5.4
+ and Python 2.6.2, and Python 2.7a1. To ensure backwards
+ compatibility, development of :mod:`repoze.bfg` is currently done
+ primarily under Python 2.4 and Python 2.5. :mod:`repoze.bfg` does
+ *not* run under any version of Python before 2.4, and does *not*
+ run under Python 3.X.
+
+:mod:`repoze.bfg` is known to run on all popular Unix-like systems
+such as Linux, MacOS X, and FreeBSD as well as on Windows platforms.
+It is also known to run on Google's App Engine and :term:`Jython`.
+
+:mod:`repoze.bfg` installation does not require the compilation of any
+C code, so you need only a Python interpreter that meets the
+requirements mentioned.
If You Don't Yet Have A Python Interpreter (UNIX)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -67,10 +60,10 @@ Source Compile Method
It's useful to use a Python interpreter that *isn't* the "system"
Python interpreter to develop your software. The authors of
-:mod:`repoze.bfg` never use the system Python for development
-purposes; always a self-compiled one. Compiling Python is easy, and
-often the "system" Python is compiled with options that aren't optimal
-for web development.
+:mod:`repoze.bfg` tend not to use the system Python for development
+purposes; always a self-compiled one. Compiling Python is usually
+easy, and often the "system" Python is compiled with options that
+aren't optimal for web development.
To compile software on your UNIX system, typically you need
development tools. Often these can be installed via the package
@@ -97,7 +90,8 @@ the following commands:
http://python.org/ftp/python/2.5.4/Python-2.5.4.tgz
[chrism@vitaminf tmp]$ tar xvzf Python-2.5.4.tgz
[chrism@vitaminf tmp]$ cd Python-2.5.4
- [chrism@vitaminf Python-2.5.4]$ ./configure --prefix=$HOME/opt/Python-2.5.4
+ [chrism@vitaminf Python-2.5.4]$ ./configure \
+ --prefix=$HOME/opt/Python-2.5.4
[chrism@vitaminf Python-2.5.4]$ make; make install
Once these steps are performed, the Python interpreter will be
@@ -119,14 +113,17 @@ extensions <http://sourceforge.net/projects/pywin32/files/>`_.
.. index::
pair: installing; UNIX
+.. _installing_unix:
+
Installing :mod:`repoze.bfg` on a UNIX System
---------------------------------------------
-It is advisable to install :mod:`repoze.bfg` into a :term:`virtualenv`
-in order to obtain isolation from any "system" packages you've got
-installed in your Python version (and likewise, to prevent
-:mod:`repoze.bfg` from globally installing versions of packages that
-are not compatible with your system Python).
+It is best practice to install :mod:`repoze.bfg` into a
+:term:`virtualenv` in order to obtain isolation from any "system"
+packages you've got installed in your Python version. Using a
+virtualenv will also prevent :mod:`repoze.bfg` from globally
+installing versions of packages that are not compatible with your
+system Python.
To set up a virtualenv to install :mod:`repoze.bfg` within, first
ensure that :term:`setuptools` is installed. Invoke ``import
@@ -161,7 +158,7 @@ using the Python interpreter you want to install setuptools into.
.. code-block:: text
- $ sudo python ez_setup.py
+ $ python ez_setup.py
Once this command is invoked, setuptools should be installed on your
system. If the command fails due to permission errors, you may need
@@ -202,7 +199,7 @@ Creating the Virtual Python Environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Once the :term:`virtualenv` package is installed in your Python, you
-can actually create a virtual environment. To do so, invoke the
+can then create a virtual environment. To do so, invoke the
following:
.. code-block:: text
@@ -245,6 +242,8 @@ downloads and installs a number of dependencies.
.. index::
pair: installing; Windows
+.. _installing_windows:
+
Installing :mod:`repoze.bfg` on a Windows System
-------------------------------------------------
@@ -304,6 +303,40 @@ Installing :mod:`repoze.bfg` on Google App Engine
:ref:`appengine_tutorial` documents the steps required to install a
:mod:`repoze.bfg` application on Google App Engine.
+Installing :mod:`repoze.bfg` on Jython
+--------------------------------------
+
+:mod:`repoze.bfg` is known to work under :term:`Jython` version 2.5.1.
+Install :term:`Jython`, and then follow the installation steps for
+:mod:`repoze.bfg` on your platform described in one of the sections
+entitled :ref:`installing_unix` or :ref:`installing_windows` above,
+replacing the ``python`` command with ``jython`` as necessary. The
+steps are exactly the same except you should use the ``jython``
+command name instead of the ``python`` command name.
+
+One caveat exists to using :mod:`repoze.bfg` under Jython: the
+:term:`Chameleon` templating engine, which is the default templating
+engine for :mod:`repoze.bfg` does not work on non-CPython platforms.
+
+The :mod:`repoze.bfg.jinja2` distribution provides templating for
+:mod:`repoze.bfg` using the :term:`Jinja2` templating system. You may
+install it like so using the ``easy_install`` command for Jython:
+
+.. code-block:: python
+
+ $ easy_install repoze.bfg.jinja2
+
+Once this is done, you can use this command to get started with a
+:mod:`repoze.bfg` sample application that uses the Jinja2 templating
+engine:
+
+.. code-block:: python
+
+ $ paster create -t bfg_jinja2_starter
+
+See the chapter entitled :ref:`project_narr` for more information
+about the ``paster create`` command.
+
What Gets Installed
~~~~~~~~~~~~~~~~~~~
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index 998cf35f7..de1ef8b92 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -3,29 +3,50 @@
single: Pylons
single: Django
single: Zope
+ single: frameworks vs. libraries
+ single: framework
:mod:`repoze.bfg` Introduction
==============================
-:mod:`repoze.bfg` is an open source Python web application framework.
-It is inspired by :term:`Zope`, :term:`Pylons`, and :term:`Django`.
-It uses the :term:`WSGI` protocol to handle requests and responses.
-
-:mod:`repoze.bfg` is written by :term:`Agendaless Consulting` and a
-community of contributors. It is developed primarily by people who
-come from the world of :term:`Zope`. Its authors also have experience
-developing applications using many other web frameworks.
+Most of the logic in a web application is completely
+application-specific. For example, the content of a web page served
+by one web application might be a representation of the contents of an
+accounting ledger, while the content of of a web page served by
+another might be a listing of songs. These applications probably
+won't serve the same set of customers. However, both the
+ledger-serving and song-serving applications can be written using
+:mod:`repoze.bfg` because it is a very general open source Python web
+*framework*. As a framework, the primary job of :mod:`repoze.bfg` is
+to make it easier for a developer to create arbitrary web
+applications.
+
+.. sidebar:: Frameworks vs. Libraries
+
+ A *framework* differs from a *library* in one very important way:
+ library code is always *called* by code that you write, while a
+ framework always *calls* code that you write. Using a set of
+ libraries to create an application is usually easier than using a
+ framework initially, because you can choose to cede control to
+ library code you have not authored very selectively. But when you
+ use a framework, you are required to cede a greater portion of
+ control to code you have not authored: code that resides in the
+ framework itself. You needn't use a framework at all to create a
+ web application using Python. A rich set of libraries already
+ exists for the platform. In practice, however, using a framework
+ to create an application is often more practical than rolling your
+ own via a set of libraries if the framework provides a set of
+ facilities that fits your application requirements.
The first release of :mod:`repoze.bfg` was made in July of 2008.
-Since its first release, it has undergone many improvements, and has
-gained features steadily. Still, it strives to maintain the following
-attributes:
+Since its first release, we've tried to ensure that BFG maintains the
+following attributes:
Simplicity
:mod:`repoze.bfg` attempts to be a *"pay only for what you eat"*
- framework in which you can be productive quickly with only partial
- knowledge. Some other frameworks tend to expect you to understand a
- great many concepts and technologies fully before you can be truly
+ framework which delivers results even if you have only partial
+ knowledge. Other frameworks may expect you to understand a great
+ many concepts and technologies fully before you can be truly
productive. :mod:`repoze.bfg` doesn't force you to use any
particular technology to produce an application, and we try to keep
the core set of concepts you need to understand to a minimum.
@@ -63,108 +84,65 @@ Trustability
exhaustively. *If it ain't tested, it's broke.* Every release of
:mod:`repoze.bfg` has 100% statement coverage via unit tests.
+Openness
+ Like :term:`Python`, the :mod:`repoze.bfg` software is distributed
+ under a `permissive open source license
+ <http://repoze.org/license.html>`_.
+
+This book usually refers to the framework by its full package name,
+:mod:`repoze.bfg`. However, it is often referred to colloquially as
+just "BFG" (the "repoze-dot" dropped) in conversation.
+
.. index::
- single: similarities to other frameworks
- single: Grok
+ single: repoze.bfg and other frameworks
single: Zope
single: Pylons
single: Django
single: MVC
-Similarities to Other Web Frameworks
-------------------------------------
+:mod:`repoze.bfg` and Other Web Frameworks
+------------------------------------------
:mod:`repoze.bfg` was inspired by :term:`Zope`, :term:`Pylons` and
-:term:`Django`.
-
-.. sidebar:: Django's Authors Explain Why It Doesn't Use "MVC" Terminology
-
- Django appears to be a MVC framework, but you call the Controller
- the "view", and the View the "template". How come you don't use the
- standard names? Well, the standard names are debatable. In our
- interpretation of MVC, the "view" describes the data that gets
- presented to the user. It's not necessarily how the data looks, but
- which data is presented. The view describes which data you see, not
- how you see it. It's a subtle distinction. So, in our case, a
- "view" is the Python callback function for a particular URL,
- because that callback function describes which data is presented.
- Furthermore, it's sensible to separate content from presentation -
- which is where templates come in. In Django, a "view" describes
- which data is presented, but a view normally delegates to a
- template, which describes how the data is presented.
-
-The :mod:`repoze.bfg` concept of :term:`traversal` is inspired by
-:term:`Zope`. Additionally, :mod:`repoze.bfg` uses a :term:`Zope
-Component Architecture` :term:`application registry` internally, as
-does Zope 2, Zope 3, and :term:`Grok`. Like Zope, :mod:`repoze.bfg`
-allows you to create applications which do not need to be forked or
-otherwise modified in order to be extended or overridden by a third
-party developer.
+:term:`Django`. As a result, :mod:`repoze.bfg` borrows several
+concepts and features from each, combining them into a unique web
+framework.
+
+Features such as :term:`traversal` and easy extensibility trace their
+origins back to :term:`Zope`. Like Zope applications,
+:mod:`repoze.bfg` applications can be easily extended. If you obey
+certain constraints, the application you produce can be reused,
+modified, re-integrated, or extended by third-party developers without
+modification to the original application itself: no fork of the
+application is required.
The :mod:`repoze.bfg` concept of :term:`URL dispatch` is inspired by
the :term:`Routes` system used by :term:`Pylons`. Like Pylons,
:mod:`repoze.bfg` is mostly policy-free. It makes no assertions about
which database you should use, and its built-in templating facilities
-are only for convenience. In essence, it only supplies a mechanism to
-map URLs to :term:`view` code, along with a convention for calling
-those views. You are free to use third-party components in your
-application that fit your needs. Also like Pylons, :mod:`repoze.bfg`
-is dependent upon :term:`WSGI`.
-
-The Django docs explain that Django is not an "MVC"
-("model/view/controller") framework in their `FAQ
-<http://www.djangoproject.com/documentation/faq/>`_. The sidebar in
-this section describes the Django authors' take on why "MVC"
-terminology doesn't match the web very well. The concepts of
-:term:`view` and :term:`model` are used by :mod:`repoze.bfg` as they
-would be by Django.
-
-The skeleton code generator of :mod:`repoze.bfg` generates a directory
-layout very similar to the directory layout suggested by the `Django
-Book <http://www.djangobook.com/>`_ .
-
-.. index::
- single: differences from other frameworks
- single: Grok
- single: Zope
- single: Pylons
- single: Django
- single: control inversion
-
-Differences from Other Web Frameworks
--------------------------------------
-
-Like :term:`Zope`, the :mod:`repoze.bfg` framework imposes more
-*control inversion* upon application developers than other Python
-frameworks such as :term:`Pylons`. For example :mod:`repoze.bfg`
-allows you to explicitly resolve a URL to a :term:`context` object
-before invoking a :term:`view`. Pylons and other Python "MVC"
-frameworks have no such intermediate step; they resolve a URL directly
-to a "controller". Another example: using the :mod:`repoze.bfg`
-security subsystem assumes that you're willing to attach an
-:term:`ACL` to a :term:`context` object; the ACL is checked by the
-framework itself instead of by user code, and access is permitted or
-denied by the framework itself rather than by user code. Such a task
-would typically be performed by user-space decorators in other Python
-web frameworks.
-
-Like Zope, but unlike :term:`Pylons` applications or most
-:term:`Django` applications, when you build a :mod:`repoze.bfg`
-application, if you obey certain constraints, the application you
-produce can be reused, modified, re-integrated, or extended by
-third-party developers without modification to the original
-application itself. See :ref:`extending_chapter` for more information
-about extending or modifying an existing :mod:`repoze.bfg`
-application.
-
-:mod:`repoze.bfg` uses a :term:`Zope Component Architecture`
-:term:`application registry` under the hood. However, while a Zope
-application developer tends to need to understand concepts such as
-"adapters", "utilities", and "interfaces" to create a non-trivial
-application, a :mod:`repoze.bfg` application developer isn't required
-to understand any of these concepts. :mod:`repoze.bfg` hides all
-interaction with the component architecture registry behind
-special-purpose API functions.
+are included only for convenience. In essence, it only supplies a
+mechanism to map URLs to :term:`view` code, along with a set of
+conventions for calling those views. You are free to use third-party
+components that fit your needs in your applications.
+
+Insofar as the term `model-view-controller
+<http://en.wikipedia.org/wiki/Model–view–controller>`_ has been
+claimed to represent a class of web frameworks, :mod:`repoze.bfg`
+generally fits into this class. The concepts of :term:`view` and
+:term:`model` are used by :mod:`repoze.bfg` as they would be by
+Django.
+
+.. sidebar:: You Say BFG is MVC, But Where's The Controller?
+
+ The :mod:`repoze.bfg` authors believe that the MVC pattern just
+ doesn't really fit the web very well. In a :mod:`repoze.bfg`
+ application, there are models, which store data, and views, which
+ present the data stored in models. However, no facility provided
+ by the framework actually maps to the concept of a "controller".
+ So :mod:`repoze.bfg` is actually an "MV" framework rather than an
+ "MVC" framework. "MVC", however, is close enough as a general
+ classification moniker for purposes of comparison with other web
+ frameworks.
Like :term:`Pylons`, but unlike :term:`Zope`, a :mod:`repoze.bfg`
application developer may use completely imperative code to perform
@@ -175,16 +153,15 @@ are used for this purpose. :mod:`repoze.bfg` *supports* :term:`ZCML`
and supports decorator-based configuration, but does not require
either. See :ref:`configuration_narr` for more information.
-Also unlike :term:`Zope` and unlike other "full-featured" frameworks
-such as :term:`Django`, :mod:`repoze.bfg` makes no assumptions about
-which persistence mechanisms you should use to build an application.
-Zope applications are typically reliant on :term:`ZODB`;
-:mod:`repoze.bfg` allows you to build :term:`ZODB` applications, but
-it has no reliance on the ZODB package. Likewise, :term:`Django`
-tends to assume that you want to store your application's data in a
-relational database. :mod:`repoze.bfg` makes no such assumption; it
-allows you to use a relational database but doesn't encourage or
-discourage an application developer about such a decision.
+Also unlike :term:`Zope` and unlike other "full-stack" frameworks such
+as :term:`Django`, :mod:`repoze.bfg` makes no assumptions about which
+persistence mechanisms you should use to build an application. Zope
+applications are typically reliant on :term:`ZODB`; :mod:`repoze.bfg`
+allows you to build :term:`ZODB` applications, but it has no reliance
+on the ZODB package. Likewise, :term:`Django` tends to assume that
+you want to store your application's data in a relational database.
+:mod:`repoze.bfg` makes no such assumption; it allows you to use a
+relational database but doesn't encourage or discourage the decision.
.. index::
single: Repoze
@@ -194,44 +171,22 @@ discourage an application developer about such a decision.
What Is Repoze?
---------------
-:term:`Repoze` is a collection of software written by `Agendaless
-Consulting <http://agendaless.com>`_ and other contributors. The
-general `Repoze website <http://repoze.org>`_ describes the Repoze
-"brand" in more detail.
-
-Software authored that uses this brand is generally placed into a
-``repoze`` namespace package. For example, both :mod:`repoze.bfg` and
-:mod:`repoze.who` are a subprojects of the more general Repoze
-project. These packages have nothing to do with each other, save for
-the fact that they are authored by the same people.
+:mod:`repoze.bfg` is a member of the collection of software published
+under the :term:`Repoze` "brand". :term:`Repoze` software is written
+by :term:`Agendaless Consulting` and a community of contributors. The
+`Repoze website <http://repoze.org>`_ describes the Repoze brand in
+more detail. Software authored that uses this brand is usually placed
+into a ``repoze`` namespace package. This namespace consists of a
+number of packages. Each package is useful in isolation. The
+``repoze`` namespace package represents that the software is written
+by a notional community rather than representing a collection of
+software that is meant to be used as a unit. For example, even though
+``repoze.bfg`` shares the same namespace as another popular Repoze
+package, ``repoze.who``, these two packages are otherwise unrelated
+and can be used separately.
.. index::
single: repoze.zope2
- single: zope 3
-
-How Does Repoze Relate to "BFG"?
---------------------------------
-
-The Repoze brand existed before :mod:`repoze.bfg`. One of the first
-packages developed as part of the Repoze brand was a package named
-:mod:`repoze.zope2`. This was a package that allowed Zope 2
-applications to run under a :term:`WSGI` server without modification.
-
-During the development of the :mod:`repoze.zope2` package, I found
-that replicating the Zope 2 "publisher" (the machinery that maps URLs
-to code) was very time-consuming and fiddly. Zope 2 had evolved over
-many years, and emulating all of its edge cases was extremely
-difficult. I finished the package, and it emulates the normal Zope 2
-publisher pretty well, but during the process, I decided that in the
-long term, creating a simpler, legacy-free publisher would be a more
-reasonable idea than continuing to use the Zope publisher. This
-publisher became what is now :mod:`repoze.bfg`.
-
-Before I started :mod:`repoze.bfg`, I considered the using Zope 3
-application server machinery, but it turned out that it had become
-more indirect than the Zope 2 machinery it aimed to replace, which
-didn't fulfill the goal of simplification. I also considered using
-Django, or Pylons, but neither of those frameworks offer much along
-the axes of traversal, contextual declarative security, or application
-extensibility; these were features I had become accustomed to as a
-Zope developer.
+ single: Zope 3
+ single: Zope 2
+
diff --git a/docs/narr/models.rst b/docs/narr/models.rst
index 0369ab178..ba89089ef 100644
--- a/docs/narr/models.rst
+++ b/docs/narr/models.rst
@@ -2,11 +2,51 @@ Models
======
A :term:`model` class is typically a simple Python class defined in a
-module. These classes are termed model *constructors*. Model
-*instances* make up the graph that :mod:`repoze.bfg` is willing to
-traverse when :term:`traversal` is used. A model instance is also
-generated as a result of :term:`url dispatch`. A model instance is
-exposed to :term:`view` code as the :term:`context` of a view.
+module. References to these classes and instances of such classes are
+omnipresent in :mod:`repoze.bfg`:
+
+- Model instances make up the graph that :mod:`repoze.bfg` is
+ willing to walk over when :term:`traversal` is used.
+
+- The ``context`` and ``containment`` arguments to
+ :meth:`repoze.bfg.configuration.Configurator.add_vew` often
+ reference a model class.
+
+- A :term:`root factory` returns a model instance.
+
+- A model instance is generated as a result of :term:`url dispatch`
+ (see the ``factory`` argument to
+ :meth:`repoze.bfg.configuration.Configurator.add_route`).
+
+- A model instance is exposed to :term:`view` code as the
+ :term:`context` of a view.
+
+Model objects typically store data and offer methods related to
+mutating that data.
+
+.. note::
+
+ A terminology overlap confuses people who write applications that
+ always use ORM packages such as SQLAlchemy, which has a very
+ different notion of the definition of a "model". When using the API
+ of common ORM packages, its conception of "model" is almost
+ certainly not the same conception of "model" used by
+ :mod:`repoze.bfg`. In particular, it can be unnatural to think of
+ :mod:`repoze.bfg` model objects as "models" if you develop your
+ application using :term:`traversal` and a relational database. When
+ you develop such applications, the object graph *might* be composed
+ completely of "model" objects (as defined by the ORM) but it also
+ might not be. The things that :mod:`repoze.bfg` refers to as
+ "models" in such an application may instead just be stand-ins that
+ perform a query and generate some wrapper *for* an ORM "model" or
+ set of ORM models. This naming overlap is slightly unfortunate.
+ However, many :mod:`repoze.bfg` applications (especially ones which
+ use :term:`ZODB`) do indeed traverse a graph full of literal model
+ nodes. Each node in the graph is a separate persistent object that
+ is stored within a database. This was the use case considered when
+ coming up with the "model" terminology. However, if we had it to do
+ all over again, we'd probably call these objects something
+ different to avoid confusion.
.. index::
pair: model; constructor
@@ -15,7 +55,8 @@ Defining a Model Constructor
----------------------------
An example of a model constructor, ``BlogEntry`` is presented below.
-It is a class which, when instantiated, becomes a model instance.
+It is implemente as a class which, when instantiated, becomes a model
+instance.
.. code-block:: python
:linenos:
@@ -36,15 +77,22 @@ the ``BlogEntry`` class can be "called", returning a model instance.
.. index::
pair: model; interfaces
+.. _models_which_implement_interfaces:
+
Model Instances Which Implement Interfaces
------------------------------------------
-Model instances can *optionally* be made to implement an
-:term:`interface`. This makes it possible to register views against
-the interface itself instead of the *class* within :term:`view`
-statements within the :term:`application registry`. If your
-application is simple enough that you see no reason to want to do
-this, you can skip reading this section of the chapter.
+Model instances can optionally be made to implement an
+:term:`interface`. An interface is used to tag a model object with a
+"type" that can later be referred to within :term:`view
+configuration`.
+
+Specifying an interface instead of a class as the ``context`` or
+``containment`` arguments within :term:`view configuration` statements
+effectively makes it possible to use a single view callable for more
+than one class of object. If your application is simple enough that
+you see no reason to want to do this, you can skip reading this
+section of the chapter.
For example, here's some code which describes a blog entry which also
declares that the blog entry implements an :term:`interface`.
@@ -73,13 +121,11 @@ constructor (above as the class ``BlogEntry``), and an
statement at class scope using the ``IBlogEntry`` interface as its
sole argument).
-An interface simply tags the model object with a "type" that can be
-referred to within the :term:`application registry`. A model object
-can implement zero or more interfaces. The interface must be an
-instance of a class that inherits from
-:class:`zope.interface.Interface`.
+The interface object used must be an instance of a class that inherits
+from :class:`zope.interface.Interface`.
-You specify that a model *implements* an interface by using the
+A model class may *implement* zero or more interfaces. You specify
+that a model implements an interface by using the
:func:`zope.interface.implements` function at class scope. The above
``BlogEntry`` model implements the ``IBlogEntry`` interface.
@@ -106,9 +152,11 @@ interface (as opposed to its class). To do so, use the
entry = BlogEntry('title', 'body', 'author')
directlyProvides(entry, IBlogEntry)
-If a model object already has instance-level interface declarations
-that you don't want to disturb, use the
-:func:`zope.interface.alsoProvides` function:
+:func:`zope.interface.directlyProvides` will replace any existing
+interface that was previously provided by an instance. If a model
+object already has instance-level interface declarations that you
+don't want to replace, use the :func:`zope.interface.alsoProvides`
+function:
.. code-block:: python
:linenos:
@@ -134,9 +182,12 @@ that you don't want to disturb, use the
directlyProvides(entry, IBlogEntry1)
alsoProvides(entry, IBlogEntry2)
-See the :ref:`views_chapter` for more information about why providing
-models with an interface can be an interesting thing to do with regard
-to :term:`view` lookup.
+:func:`zope.interface.alsoProvides` will augment the set of interfaces
+directly provided by an instance instead of overwriting them like
+:func:`zope.interface.directlyProvides` does.
+
+For more information about how model interfaces can be used by view
+configuration, see :ref:`using_model_interfaces`.
.. index::
single: model graph
@@ -150,10 +201,10 @@ Defining a Graph of Model Instances for Traversal
When :term:`traversal` is used (as opposed to a purely :term:`url
dispatch` based application), mod:`repoze.bfg` expects to be able to
-traverse a graph of model instances. Traversal begins at a root
-model, and descends into the graph recursively via each found model's
-``__getitem__`` method. :mod:`repoze.bfg` imposes the following
-policy on model instance nodes in the graph:
+traverse a graph composed of model instances. Traversal begins at a
+root model, and descends into the graph recursively via each found
+model's ``__getitem__`` method. :mod:`repoze.bfg` imposes the
+following policy on model instance nodes in the graph:
- Nodes which contain other nodes (aka "container" nodes) must supply
a ``__getitem__`` method which is willing to resolve a unicode name
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 02d9e30ba..287561862 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -3,22 +3,63 @@
Creating a :mod:`repoze.bfg` Project
====================================
-While it's possible to create a :mod:`repoze.bfg` application
-completely manually, it's useful to be able to create a "skeleton"
-:mod:`repoze.bfg` application using an application skeleton generator.
-"Skeleton" projects can be created using the ``paster create`` command
-in conjunction with :term:`Paste` templates. Various project
-templates that come with :mod:`repoze.bfg` make different
-configuration assumptions about what type of application you're trying
-to construct.
-
-All existing project templates make the assumption that you want your
-code to live in a Python :term:`package`. Even if your application is
-extremely simple, it is useful to place code that drives the
-application within a package, because a package is more easily
-extended with new code and an application that lives inside a package
-can be distributed more easily than one which does not live within a
-package.
+It's possible to create a :mod:`repoze.bfg` application completely
+manually, but it's usually more convenient to use a template to
+generate a basic :mod:`repoze.bfg` application structure.
+
+:mod:`repoze.bfg` comes with templates that you can use to generate a
+project. Each template makes different configuration assumptions
+about what type of application you're trying to construct.
+
+These templates are rendered using the :term:`PasteDeploy` ``paster``
+script, and so therefore they are often referred to as "paster
+templates".
+
+.. index::
+ single: paster templates
+ single: bfg_starter
+ single: bfg_zodb
+ single: bfg_alchemy
+ single: bfg_routesalchemy
+
+.. _additional_paster_templates:
+
+Paster Templates Included with :mod:`repoze.bfg`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The convenience ``paster`` templates included with :mod:`repoze.bfg`
+differ from each other on two axes:
+
+- the persistence mechanism they offer (no persistence mechanism,
+ :term:`ZODB`, or :term:`SQLAlchemy`).
+
+- the mechanism they use to map URLs to code (:term:`traversal` or
+ :term:`URL dispatch`).
+
+The included templates are these:
+
+``bfg_starter``
+ URL mapping via :term:`traversal` and no persistence mechanism.
+
+``bfg_zodb``
+ URL mapping via :term:`traversal` and persistence via :term:`ZODB`
+
+``bfg_routesalchemy``
+ URL mapping via :term:`URL dispatch` and persistence via
+ :term:`SQLAlchemy`
+
+``bfg_alchemy``
+ URL mapping via :term:`traversal` and persistence va
+ :term:`SQLAlchemy`
+
+All existing project templates use :term:`ZCML` instead of
+:term:`imperative configuration`. All existing project templates also
+make the assumption that you want your code to live in a Python
+:term:`package`. Even if your application is extremely simple, it is
+useful to place code that drives the application within a package,
+because a package is more easily extended with new code and an
+application that lives inside a package can be distributed more easily
+than one which does not live within a package.
.. index::
pair: project; creating
@@ -28,14 +69,27 @@ package.
Creating the Project
--------------------
-To start a :mod:`repoze.bfg` :term:`project`, use the ``paster
-create`` facility using the interpreter from the virtualenv
-(``bfgenv``) directory you created in :ref:`installing_chapter`.
+In in :ref:`installing_chapter`, you created a virtual Python
+environment via the ``virtualenv`` command. To start a
+:mod:`repoze.bfg` :term:`project`, use the ``paster`` facility
+installed within the virtualenv. In :ref:`installing_chapter` we
+called the virtualenv directory ``bfgenv``; the following command
+assumes that our current working directory is that directory.
+
+We'll choose the ``bfg_starter`` template for this purpose.
.. code-block:: text
$ bin/paster create -t bfg_starter
+The above command uses the ``paster`` command to create a project
+using the ``bfg_starter`` template. The ``create`` version of paster
+invokes the creation of a project from a template. To use a different
+template, such as ``bfg_routesalchemy``, you'd just change the last
+argument. For example:
+
+ $ bin/paster create -t bfg_routesalchemy
+
``paster create`` will ask you a single question: the *name* of the
project. You should use a string without spaces and with only letters
in it. Here's sample output from a run of ``paster create`` for a
@@ -57,15 +111,21 @@ project we name ``MyProject``:
# ... more output ...
Running /Users/chrism/projects/repoze/bfg/bin/python setup.py egg_info
-As a result of invoking ``paster create``, a project is created in a
-directory named ``MyProject``. That directory is a :term:`setuptools`
-:term:`project` directory from which a Python setuptools
+.. note:: You can skip the interrogative question about a project
+ name during ``paster create`` by adding the project name to the
+ command line, e.g. ``paster create -t bfg_starter MyProject``.
+
+As a result of invoking the ``paster create`` command above, a project
+is created in a directory named ``MyProject``. That directory is a
+:term:`setuptools` :term:`project` directory from which a setuptools
:term:`distribution` can be created. The ``setup.py`` file in that
directory can be used to distribute your application, or install your
-application for deployment or development. A sample
-:term:`PasteDeploy` ``.ini`` file named ``MyProject.ini`` will also be
-created in the project directory. You will use the ``paster serve``
-command against this ``.ini`` file to run your application.
+application for deployment or development.
+
+A sample :term:`PasteDeploy` ``.ini`` file named ``MyProject.ini``
+will also be created in the project directory. You will use the
+``paster serve`` command against this ``.ini`` file to run your
+application.
The ``MyProject`` project directory contains an additional
subdirectory named ``myproject`` (note the case difference)
@@ -73,31 +133,9 @@ representing a Python :term:`package` which holds very simple
:mod:`repoze.bfg` sample code. This is where you'll edit your
application's Python code and templates.
-.. note:: You can skip the interrogative question about a project
- name during ``paster create`` by adding the project name to the
- command line, e.g. ``paster create -t bfg_starter MyProject``.
-
.. index::
- single: paster templates
- single: project templates
-
-.. _additional_paster_templates:
-
-Additional Paster Templates
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Convenience :term:`Paste` templates for projects which will depend on
-:term:`ZODB` or `SQLAlchemy <http://www.sqlalchemy.org/>`_ also exist.
-
-- Use ``paster create -t bfg_zodb`` to create a project that depends on
- ZODB.
-
-- Use ``paster create -t bfg_routesalchemy`` to create a project that
- depends on SQLAlchemy and :term:`URL dispatch` (no :term:`traversal`).
-
-- Use ``paster create -t bfg_alchemy`` to create a project that
- depends on SQLAlchemy but *not* :term:`URL dispatch` (uses only
- :term:`traversal`).
+ single: setup.py develop
+ single: development install
Installing your Newly Created Project for Development
-----------------------------------------------------
@@ -106,7 +144,8 @@ Using the interpreter from the :term:`virtualenv` you create during
:ref:`installing_chapter`, invoke the following command when inside
the project directory. The file named ``setup.py`` will be in the
root of the paster-generated project directory. The ``python`` you're
-invoking should be the one from your virtualenv.
+invoking should be the one that lives in the ``bin`` directory of your
+virtual Python environment.
.. code-block:: text
@@ -120,9 +159,9 @@ Elided output from a run of this command is shown below:
...
Finished processing dependencies for MyProject==0.1
-This will install your application's :term:`package` into the
-interpreter so it can be found and run as a :term:`WSGI` application
-inside a WSGI server.
+This will install the :term:`distribution` representing your
+application's into the interpreter's library set so it can be found
+and run by :term:`PasteDeploy` via ``paster serve``.
.. index::
pair: running; tests
@@ -130,8 +169,8 @@ inside a WSGI server.
Running The Tests For Your Application
--------------------------------------
-To run unit tests for your application, you should invoke them like
-so:
+To run unit tests for your application, you should invoke them using
+the ``python`` that lives in the ``bin`` directory of your virtualenv:
.. code-block:: text
@@ -158,20 +197,23 @@ Here's sample output from a test run:
OK
-The tests are found in the ``tests.py`` module in your ``paster
-create`` -generated project. One sample test exists.
+The tests themselves are found in the ``tests.py`` module in your
+``paster create`` -generated project. Within a project generated by
+the ``bfg_starter`` template, a single sample test exists.
.. index::
single: interactive shell
+ single: IPython
+ single: paster bfgshell
The Interactive Shell
---------------------
Once you've installed your program for development using ``setup.py
-develop``, you can use an interactive shell to examine your
-:mod:`repoze.bfg` application :term:`model` objects from a Python
-prompt. To do so, use the ``paster`` shell command with the
-``bfgshell`` argument:
+develop``, you can use an interactive Python shell to examine your
+:mod:`repoze.bfg` application :term:`model` and :term:`view` objects
+from a Python prompt. To do so, use the ``paster`` shell command with
+the ``bfgshell`` argument:
The first argument to ``bfgshell`` is the path to your application's
``.ini`` file. The second is the section name inside the ``.ini``
@@ -217,8 +259,6 @@ standard Python interpreter shell unconditionally.
[chrism@vitaminf bfgshellenv]$ ../bin/paster --plugin=repoze.bfg bfgshell \
MyProject.ini main
-Press "Ctrl-D" to exit the interactive shell.
-
You should always use a section name argument that refers to the
actual ``app`` section within the Paste configuration file that points
at your :mod:`repoze.bfg` application *without any middleware
@@ -252,54 +292,100 @@ against the above ``.ini`` file, an error will likely occur. Use the
most specific reference to the application within the ``.ini`` file
possible as the section name argument.
+Press "Ctrl-D" to exit the interactive shell.
+
+.. index::
+ single: running an application
+ single: paster serve
+ single: reload
+ single: startup
+ single: mod_wsgi
+
Runnning The Project Application
--------------------------------
-Once the project is installed for development, you can run the
+Once a project is installed for development, you can run the
application it represents using the ``paster serve`` command against
-the generated ``MyProject.ini`` configuration file:
+the generated configuration file. In our case, this file is named
+``MyProject.ini``:
.. code-block:: text
$ ../bin/paster serve MyProject.ini
-.. sidebar:: Using ``mod_wsgi``
-
- You can also use :term:`mod_wsgi` to serve your :mod:`repoze.bfg`
- application using the Apache web server rather than the
- "pure-Python" server that is started as a result of ``paster
- serve``. See :ref:`modwsgi_tutorial` for details. However, it is
- usually easier to develop an application using the ``paster serve``
- webserver, as exception and debugging output will be sent to the
- console.
-
-Here's sample output from a run:
+Here's sample output from a run of ``paster serve``:
.. code-block:: text
- $ paster serve MyProject.ini
+ $ ../bin/paster serve MyProject.ini
Starting server in PID 16601.
serving on 0.0.0.0:6543 view at http://127.0.0.1:6543
By default, generated :mod:`repoze.bfg` applications will listen on
-port 6543.
+TCP port 6543.
-.. note:: During development, it's often useful to run ``paster
- serve`` using its ``--reload`` option. When any Python module your
- project uses, changes, it will restart the server, which makes
- development easier, as changes to Python code under
- :mod:`repoze.bfg` is not put into effect until the server restarts.
+During development, it's often useful to run ``paster serve`` using
+its ``--reload`` option. When ``--reload`` is passed to ``paster
+serve``, changes to any Python module your project uses will cause the
+server to restart. This typically makes development easier, as
+changes to Python code made within a :mod:`repoze.bfg` application is
+not put into effect until the server restarts.
+
+For example:
+
+.. code-block:: text
+
+ $ ../bin/paster serve MyProject.ini --reload
+ Starting subprocess with file monitor
+ Starting server in PID 16601.
+ serving on 0.0.0.0:6543 view at http://127.0.0.1:6543
+
+For more detailed information about the startup process, see
+:ref:`startup_chapter`. For more information about environment
+variables and configuration file settings that influence startup and
+runtime behavior, see :ref:`environment_chapter`.
+
+.. topic:: Disusing ``paster serve`` and Using Alternate Servers
+
+ The code generated by :mod:`repoze.bfg` ``paster`` templates
+ assumes that you will be using the ``paster serve`` command to
+ start your application while you do development. However, ``paster
+ serve`` is by no means the only way to start up and serve a
+ :mod:`repoze.bfg` application. As we saw in
+ :ref:`configuration_narr`, ``paster serve`` needn't be invoked at
+ all to run a :mod:`repoze.bfg` application. The use of ``paster
+ serve`` to run a :mod:`repoze.bfg` application is purely
+ conventional based on the output of its ``paster`` templates.
+
+ Any :term:`WSGI` server is capable of running a :mod:`repoze.bfg`
+ application. Some WSGI servers don't require the
+ :term:`PasteDeploy` framework's ``paster serve`` command to do
+ server process management at all. Each :term:`WSGI` server has its
+ own documentation about how it creates a process to run an
+ application, and there are many of them, so we cannot provide the
+ details for each here. But the concepts are largely the same,
+ whatever server you happen to use.
+
+ One popular production alternative to a ``paster``-invoked server
+ is :term:`mod_wsgi`. can also use :term:`mod_wsgi` to serve your
+ :mod:`repoze.bfg` application using the Apache web server rather
+ than any "pure-Python" server that is started as a result of
+ ``paster serve``. See :ref:`modwsgi_tutorial` for details.
+ However, it is usually easier to *develop* an application using a
+ ``paster serve`` -invoked webserver, as exception and debugging
+ output will be sent to the console.
Viewing the Application
-----------------------
-Visit ``http://localhost:6543/`` in your browser. You will see
-something in your browser like what is displayed below:
+Once your application is running via ``paster serve``, you may visit
+``http://localhost:6543/`` in your browser. You will see something in
+your browser like what is displayed below:
.. image:: project.png
That's the page shown by default when you visit an unmodified ``paster
-create``-generated application.
+create`` -generated ``bfg_starter`` application.
.. index::
single: project structure
@@ -307,13 +393,15 @@ create``-generated application.
The Project Structure
---------------------
-Our generated :mod:`repoze.bfg` application is a setuptools
-:term:`project` (named ``MyProject``), which contains a Python
-:term:`package` (which is *also* named ``myproject``, but lowercased;
-the paster template generates a project which contains a package that
-shares its name except for case).
+Our generated :mod:`repoze.bfg` ``bfg_starter`` application is a
+setuptools :term:`project` (named ``MyProject``), which contains a
+Python :term:`package` (which is *also* named ``myproject``, but
+lowercased; the paster template generates a project which contains a
+package that shares its name except for case). All :mod:`repoze.bfg`
+``paster`` -generated projects share a similar structure.
-The ``MyProject`` project has the following directory structure::
+The ``MyProject`` project we've generated has the following directory
+structure::
MyProject/
|-- CHANGES.txt
@@ -353,8 +441,6 @@ describe, run, and test your application.
application. It is a standard :term:`setuptools` ``setup.py``
file.
-We won't describe the ``CHANGES.txt`` or ``README.txt`` files.
-
.. index::
single: PasteDeploy
single: ini file
@@ -366,8 +452,8 @@ We won't describe the ``CHANGES.txt`` or ``README.txt`` files.
The ``MyProject.ini`` file is a :term:`PasteDeploy` configuration
file. Its purpose is to specify an application to run when you invoke
-``paster serve`` when you start an application, as well as the
-deployment settings provided to that application.
+``paster serve``, as well as the deployment settings provided to that
+application.
The generated ``MyProject.ini`` file looks like so:
@@ -383,7 +469,7 @@ within the configuration file. By default it contains one key
``debug``, which is set to ``true``. This key is used by various
components to decide whether to act in a "debugging" mode.
``repoze.bfg`` itself does not do anything at all with this parameter,
-and neither does any generated sample application.
+and neither does any template-generated application.
The ``[app:main]`` section represents configuration for your
application. This section name represents the ``main`` application
@@ -399,7 +485,7 @@ indicates that this is an entry point *URI* specifier, where the
"scheme" is "egg"; there are no other schemes currently, so the
``egg:`` prefix is arguably not very useful).
-.. note::
+.. sidebar:: ``setuptools`` Entry Points and PasteDeploy ``.ini`` Files
This part of configuration can be confusing so let's try to clear
things up a bit. Take a look at the generated ``setup.py`` file
@@ -433,10 +519,10 @@ module). You can provide startup-time configuration parameters to
your application by requiring more settings in this section.
The ``reload_templates`` setting in the ``[app:main]`` section is a
-:mod:`repoze.bfg`-specific setting which is passed into the framework.
-If it exists, and is ``true``, :term:`Chameleon` template changes will
-not require an application restart to be detected. See
-:ref:`reload_templates_section` for more information.
+:mod:`repoze.bfg` -specific setting which is passed into the
+framework. If it exists, and its value is ``true``, :term:`Chameleon`
+template changes will not require an application restart to be
+detected. See :ref:`reload_templates_section` for more information.
.. warning:: The ``reload_templates`` option should be turned off for
production applications, as template rendering is slowed when it is
@@ -454,10 +540,10 @@ new thread for each request.
.. note::
- In general, :mod:`repoze.bfg` applications should be
- threading-aware. It is not required that a :mod:`repoze.bfg`
- application be nonblocking as all application code will run in its
- own thread, provided by the server you're using.
+ In general, :mod:`repoze.bfg` applications generated from ``paster
+ templates`` should be threading-aware. It is not required that a
+ :mod:`repoze.bfg` application be nonblocking as all application code
+ will run in its own thread, provided by the server you're using.
See the :term:`PasteDeploy` documentation for more information about
other types of things you can put into this ``.ini`` file, such as
@@ -493,9 +579,9 @@ on the command line.
Within the arguments to this function call, information about your
application is kept. While it's beyond the scope of this
documentation to explain everything about setuptools setup files, we'll
-provide a whirlwind tour of what exists in this file here.
+provide a whirlwind tour of what exists in this file in this section.
-Your application's name (this can be any string) is specified in the
+Your application's name can be any string; it is specified in the
``name`` field. The version number is specified in the ``version``
value. A short description is provided in the ``description`` field.
The ``long_description`` is conventionally the content of the README
@@ -521,9 +607,11 @@ represents our project's application.
Usually you only need to think about the contents of the ``setup.py``
file when distributing your application to other people, or when
versioning your application for your own use. For fun, you can try
-this command now::
+this command now:
+
+.. code-block:: python
- python setup.py sdist
+ $ python setup.py sdist
This will create a tarball of your application in a ``dist``
subdirectory named ``MyProject-0.1.tar.gz``. You can send this
@@ -593,7 +681,7 @@ the :term:`application registry`. It looks like so:
language. ``http://namespaces.repoze.org/bfg`` is the default XML
namespace. Add-on packages may require other namespaces.
-#. Line 4 initializes :mod:`repoze.bfg`-specific configuration
+#. Line 4 initializes :mod:`repoze.bfg` -specific configuration
directives by including the :mod:`repoze.bfg.includes` package.
This causes all of the ZCML within the ``configure.zcml`` of the
:mod:`repoze.bfg.includes` package (which can be found in the main
@@ -606,15 +694,16 @@ the :term:`application registry`. It looks like so:
attribute). It is registered so that it will be found when the
:term:`context` of the request is an instance of the
:class:`myproject.models.MyModel` class. The ``view`` attribute
- points at a Python function that does all the work for this view.
- Note that the values of both the ``context`` attribute and the
- ``view`` attribute begin with a single period. Names that begin
- with a period are "shortcuts" which point at files relative to the
- :term:`package` in which the ``configure.zcml`` file lives. In
- this case, since the ``configure.zcml`` file lives within the
- :mod:`myproject` package, the shortcut ``.models.MyModel`` could
- also be spelled ``myproject.models.MyModel`` (forming a full Python
- dotted-path name to the ``MyModel`` class). Likewise the shortcut
+ points at a Python function that does all the work for this view,
+ also known as a :term:`view callable`. Note that the values of
+ both the ``context`` attribute and the ``view`` attribute begin
+ with a single period. Names that begin with a period are
+ "shortcuts" which point at files relative to the :term:`package` in
+ which the ``configure.zcml`` file lives. In this case, since the
+ ``configure.zcml`` file lives within the :mod:`myproject` package,
+ the shortcut ``.models.MyModel`` could also be spelled
+ ``myproject.models.MyModel`` (forming a full Python dotted-path
+ name to the ``MyModel`` class). Likewise the shortcut
``.views.my_view`` could be replaced with
``myproject.views.my_view``.
@@ -640,29 +729,29 @@ the :term:`application registry`. It looks like so:
~~~~~~~~~~~~
Much of the heavy lifting in a :mod:`repoze.bfg` application comes in
-the form of *views*. A :term:`view` is the bridge between the content
-in the model, and the response given back to a browser.
+the form of *view callables*. A :term:`view callable` is the bridge
+between the content in the model, and the response given back to a
+browser.
.. literalinclude:: MyProject/myproject/views.py
:linenos:
-Lines 1-2 provide the ``my_view`` that was registered as the view.
-``configure.zcml`` said that the default URL for instances that are of
-the class :class:`myproject.models.MyModel` should run this
-:func:`myproject.views.my_view` function.
+Lines 1-2 provide the ``my_view`` that was registered as the view
+callable. ``configure.zcml`` said that the default URL for instances
+that are of the class :class:`myproject.models.MyModel` should run
+this :func:`myproject.views.my_view` function.
-The function is handed two pieces of information: the :term:`context`
-and the :term:`request`. The *context* is the term :term:`model`
-found via :term:`traversal` (or via :term:`URL dispatch`). The
-*request* is an instance of the :term:`WebOb` ``Request`` class
-representing the browser's request to our server.
+This view callable function is handed a single piece of information:
+the the :term:`request`. The *request* is an instance of the
+:term:`WebOb` ``Request`` class representing the browser's request to
+our server.
This view returns a dictionary. When this view is invoked, a
-:term:`renderer` renders the dictionary returned by the view, and
-returns the result as the :term:`response`. This view is configured
-to invoke a renderer which uses a :term:`Chameleon` ZPT template
-(``templates/my_template.pt``, as specified in the ``configure.zcml``
-file).
+:term:`renderer` converts the dictionary returned by the view into
+HTML, and returns the result as the :term:`response`. This view is
+configured to invoke a renderer which uses a :term:`Chameleon` ZPT
+template (``templates/my_template.pt``, as specified in the
+``configure.zcml`` file).
See :ref:`views_which_use_a_renderer` for more information about how
views, renderers, and templates relate and cooperate.
@@ -715,9 +804,8 @@ so the sample application uses an instance of
~~~~~~~~~~
We need a small Python module that configures our application and
-advertises itself to our :term:`PasteDeploy` ``.ini`` file. For
-convenience, we also make it possible to run this module directory
-without the PasteDeploy configuration file:
+advertises itself to our :term:`PasteDeploy` ``.ini`` file. This is
+the file named ``run.py``:
.. literalinclude:: MyProject/myproject/run.py
:linenos:
@@ -736,19 +824,17 @@ without the PasteDeploy configuration file:
``templates/mytemplate.pt``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The single :term:`Chameleon` template in the project looks like so:
-
-.. literalinclude:: MyProject/myproject/templates/mytemplate.pt
- :linenos:
- :language: xml
-
-It displays a default page when rendered. It is referenced by the
-``view`` declaration's ``renderer`` attribute in the
-``configure.zcml`` file. Templates are accessed and used by view
-declarations and sometimes by view functions themselves. See
+The single :term:`Chameleon` template exists in the project. Its
+contents are too long to show here, but it displays a default page
+when rendered. It is referenced by the ``view`` declaration's
+``renderer`` attribute in the ``configure.zcml`` file. See
:ref:`views_which_use_a_renderer` for more information about
renderers.
+Templates are accessed and used by view configurations and sometimes
+by view functions themselves. See :ref:`templates_used_directly` and
+:ref:`templates_used_as_renderers`.
+
``templates/static``
~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/narr/router.rst b/docs/narr/router.rst
index 98752216a..c9ffe2089 100644
--- a/docs/narr/router.rst
+++ b/docs/narr/router.rst
@@ -25,32 +25,31 @@ processing?
#. A :term:`request` object is created based on the WSGI environment.
-#. To service :term:`url dispatch`, the :mod:`repoze.bfg`
- :term:`router` calls a :term:`URL dispatch` "root factory wrapper"
- callable, which acts as a :term:`root factory`. The job of the
- mapper is to examine the ``PATH_INFO`` implied by the request to
- determine whether any user-defined :term:`route` matches the
- current WSGI environment. The :term:`router` passes the request as
- an argument to the mapper.
+#. A :class:`repoze.bfg.interfaces.INewRequest` :term:`event` is sent
+ to any subscribers.
+
+#. If any :term:`route` has been defined within application
+ configuration, the :mod:`repoze.bfg` :term:`router` calls a
+ :term:`URL dispatch` "route mapper." The job of the mapper is to
+ examine the ``PATH_INFO`` implied by the request to determine
+ whether any user-defined :term:`route` matches the current WSGI
+ environment. The :term:`router` passes the request as an argument
+ to the mapper.
#. If any route matches, the WSGI environment is mutated; a
``bfg.routes.route`` key and a ``bfg.routes.matchdict`` are added
to the WSGI environment, and an attribute named ``matchdict`` is
- added to the request. If a route *doesn't* match, neither of these
- keys is added to the WSGI environment and the request object is not
- mutated.
-
-#. Regardless of whether any route matched or not, the :term:`URL
- dispatch` mapper returns a root object. If a particular
- :term:`route` named a ``factory`` argument, this factory is used to
- generate the root object, otherwise a default :term:`root factory`
- is used. If a root factory argument was passed to the
- :term:`Configurator` constructor, that callable is used to generate
- the root object. If the root factory argument passed to the
- Configurator constructor is ``None``, a default root factory is
- used to generate a root.
-
-#. A ``NewRequest`` :term:`event` is sent to any subscribers.
+ added to the request. A root object associated with the route
+ found is also generated. If a the :term:`route configuration`
+ which matched has an associated a ``factory`` argument, this
+ factory is used to generate the root object, otherwise a default
+ :term:`root factory` is used.
+
+#. If a route match was *not* found, and a ``root_factory`` argument
+ was passed to the :term:`Configurator` constructor, that callable
+ is used to generate the root object. If the ``root_factory``
+ argument passed to the Configurator constructor is ``None``, a
+ default root factory is used to generate a root object.
#. The :mod:`repoze.bfg` router calls a "traverser" function with the
root object and the request. The traverser function attempts to
@@ -67,26 +66,29 @@ processing?
they can be accessed via e.g. ``request.context`` within
:term:`view` code.
-#. If an :term:`authorization policy` is in use, :mod:`repoze.bfg`
- passes the context, the request, and the view_name to a function
- which determines whether the view being asked for can be executed
- by the requesting user, based on credential information in the
- request and security information attached to the context. If it
- returns True, :mod:`repoze.bfg` allows processing to continue. If
- it returns False, it uses a "forbidden" view callable to generate a
- response, and returns that response.
-
-#. If view execution is determined to be allowed, :mod:`repoze.bfg`
- looks up a :term:`view` callable using the context, the request,
- and the view name. If a view callable doesn't exist for this
- combination of objects (based on the type of the context, the type
- of the request, and the value of the view name), :mod:`repoze.bfg`
- uses a "not found" view callable to generate a response, and
- returns that response.
+#. A :class:`repoze.bfg.interfaces.IAfterTraversal` :term:`event` is
+ sent to any subscribers.
+
+#. :mod:`repoze.bfg` looks up a :term:`view` callable using the
+ context, the request, and the view name. If a view callable
+ doesn't exist for this combination of objects (based on the type of
+ the context, the type of the request, and the value of the view
+ name, and any :term:`predicate` attributes applied to the view
+ configuration), :mod:`repoze.bfg` uses a "not found" view callable
+ to generate a response, and returns that response.
#. If a view callable was found, :mod:`repoze.bfg` calls the view
- function. The view function's response is a :term:`response`
- object.
+ function.
+
+#. If an :term:`authorization policy` is in use, and the view was
+ protected by a :term:`permission`, :mod:`repoze.bfg` passes the
+ context, the request, and the view_name to a function which
+ determines whether the view being asked for can be executed by the
+ requesting user, based on credential information in the request and
+ security information attached to the context. If it returns
+ ``True``, :mod:`repoze.bfg` calls the view callable to obtain a
+ response. If it returns ``False``, it uses a :term:`forbidden
+ view` callable to generate a response.
#. A :class:`repoze.bfg.interfaces.INewResponse` :term:`event` is sent
to any subscribers.
@@ -98,8 +100,8 @@ processing?
.. image:: router.png
This is a very high-level overview that leaves out various details.
-For more detail about subsystems invoked by the BFG router (like
-traversal, URL dispatch, views, and events), see
+For more detail about subsystems invoked by the BFG router such as
+traversal, URL dispatch, views, and event processing, see
:ref:`url_mapping_chapter`, :ref:`traversal_chapter`,
:ref:`urldispatch_chapter`, :ref:`views_chapter`, and
:ref:`events_chapter`.
diff --git a/docs/narr/scanning.rst b/docs/narr/scanning.rst
deleted file mode 100644
index 83908540e..000000000
--- a/docs/narr/scanning.rst
+++ /dev/null
@@ -1,202 +0,0 @@
-.. _scanning_chapter:
-
-Configuration, Decorations And Code Scanning
-============================================
-
-:mod:`repoze.bfg` provides a number of "modes" for performing
-application configuration. These modes can be used interchangeably or
-even combined, as necessary.
-
-For example:
-
-- A ``<view>`` :term:`ZCML declaration` adds a :term:`view
- configuration` to the current :term:`application registry`.
-
-- A call to the :meth:`repoze.bfg.configuration.Configurator.add_view`
- method adds a :term:`view configuration` to the current
- :term:`application registry`.
-
-- the :class:`repoze.bfg.view.bfg_view` :term:`decorator` adds
- :term:`configuration decoration` to the function or method it
- decorates. This particular decoration can result in a :term:`view
- configuration` to be added to the current :term:`application
- registry` if the package the code lives in is run through a
- :term:`scan`.
-
-.. index::
- single: decorator
- single: scan
-
-Decorations and Code Scanning
------------------------------
-
-To lend more *locality of reference* to a :term:`configuration
-declaration`, :mod:`repoze.bfg` allows you to insert
-:term:`configuration decoration` statements very close to code that is
-referred to by the declaration itself.
-
-The mere existence of configuration decoration doesn't cause any
-configuration registration to be made. Before they have any effect on
-the configuration of a :mod:`repoze.bfg` application, a configuration
-decoration within application code must be found through a process
-known as *scanning*.
-
-:mod:`repoze.bfg` is willing to :term:`scan` a module or a package and
-its subpackages for decorations when the
-:meth:`repoze.bfg.configuration.Configurator.scan` method is invoked:
-scanning implies searching for configuration declarations in a package
-and its subpackages. :term:`ZCML` can also invoke a :term:`scan` via
-its ``<scan>`` directive.
-
-The scanning machinery imports each module and subpackage in a package
-or module recursively, looking for special attributes attached to
-objects defined within a module. These special attributes are
-typically attached to code via the use of a :term:`decorator`. For
-example, the :class:`repoze.bfg.view.bfg_view` decorator can be
-attached to a function or instance method:
-
-.. code-block:: python
- :linenos:
-
- from repoze.bfg.view import bfg_view
- from webob import Response
-
- @bfg_view(name='hello', request_method='GET')
- def hello(request):
- return Response('Hello')
-
-The :class:`repoze.bfg.view.bfg_view` decorator above simply adds an
-attribute to the ``hello`` function, making it available for a
-:term:`scan` to find it later.
-
-Once scanning is invoked, and :term:`configuration decoration` is
-found by the scanner, a set of calls are made to a
-:term:`Configurator` on behalf of the developer: these calls represent
-the intent of the configuration decoration. In the example above,
-this is best represented as the scanner translating the arguments to
-:class:`repoze.bfg.view.bfg_view` into a call to the
-:meth:`repoze.bfg.configuration.Configurator.add_view` method,
-effectively:
-
-.. ignore-next-block
-.. code-block:: python
-
- config.add_view(hello, name='hello', request_method='GET')
-
-Scanning for :term:`configuration decoration` is performed via the
-:meth:`repoze.bfg.configuration.Configurator.scan` method or via a
-``<scan>`` :term:`ZCML declaration`. See
-:ref:`config_mode_equivalence` for examples.
-
-.. index::
- pair: configuration mode; equivalence
- single: add_view
- single: bfg_view
- pair: ZCML directive; view
-
-.. _config_mode_equivalence:
-
-Configuration Mode Equivalence
-------------------------------
-
-A combination of imperative configuration, declarative configuration
-via ZCML and scanning can be used to configure any application. Each
-of the below examples produces the same application configuration.
-
-.. topic:: Completely Imperative Configuration
-
- .. code-block:: python
- :linenos:
-
- # helloworld.py
-
- from repoze.bfg.view import bfg_view
- from webob import Response
-
- def hello(request):
- return Response('Hello')
-
- if __name__ == '__main__':
- from repoze.bfg.configuration import Configurator
- config = Configurator()
- config.add_view(hello, name='hello', request_method='GET')
-
-.. topic:: Configuration via ZCML
-
- .. code-block:: python
- :linenos:
-
- # helloworld.py
-
- from webob import Response
-
- def hello(request):
- return Response('Hello')
-
- if __name__ == '__main__':
- from repoze.bfg.configuration import Configurator
- config = Configurator()
- config.load_zcml('configure.zcml')
-
- .. code-block:: xml
- :linenos:
-
- <configure xmlns="http://namespaces.repoze.org">
-
- <!-- configure.zcml -->
-
- <include package="repoze.bfg.includes"/>
-
- <view name="hello"
- request_method="GET"/>
-
- </configure>
-
-.. topic:: Using Decorations (Imperatively Starting a Scan)
-
- .. code-block:: python
- :linenos:
-
- from repoze.bfg.view import bfg_view
- from webob import Response
-
- @bfg_view(name='hello', request_method='GET')
- def hello(request):
- return Response('Hello')
-
- if __name__ == '__main__':
- from repoze.bfg.configuration import Configurator
- config = Configurator()
- config.scan()
-
-.. topic:: Using Decorations (Starting a Scan via ZCML)
-
- .. code-block:: python
- :linenos:
-
- # helloworld.py
-
- from repoze.bfg.view import bfg_view
- from webob import Response
-
- @bfg_view(name='hello', request_method='GET')
- def hello(request):
- return Response('Hello')
-
- if __name__ == '__main__':
- from repoze.bfg.configuration import Configurator
- config = Configurator()
- config.load_zcml('configure.zcml')
-
- .. code-block:: xml
- :linenos:
-
- <configure xmlns="http://namespaces.repoze.org">
-
- <!-- configure.zcml -->
-
- <include package="repoze.bfg.includes"/>
- <scan package="."/>
-
- </configure>
-
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index f0d800190..cb10ff0b5 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -6,11 +6,35 @@ Security
:mod:`repoze.bfg` provides an optional declarative authorization
system that prevents a :term:`view` from being invoked when the user
represented by credentials in the :term:`request` does not have an
-appropriate level of access with respect to a specific
-:term:`context`.
+appropriate level of access within a particular :term:`context`.
+Here's how it works at a high level:
+
+- A :term:`request` is generated when a user visits our application.
+
+- Based on the request, a :term:`context` is located. Exactly how a
+ context is located depends whether you are using :term:`traversal`
+ or :term:`URL dispatch`, but in either case, one is found. See
+ :ref:`url_mapping_chapter` for more information.
+
+- A :term:`view callable` is located using the the context as well as
+ other attributes of the request.
+
+- If an :term:`authorization policy` is in effect and the :term:`view
+ configuration` associated with the view callable that was found has
+ a :term:`permission` associated with it, the authorization policy is
+ passed the context: it will either allow or deny access.
+
+- If access is allowed, the view callable is invoked.
+
+- If access is denied, view callable is not invoked; instead the
+ :term:`forbidden view` is invoked.
Authorization is enabled by modifying your application to include a
:term:`authentication policy` and :term:`authorization policy`.
+:mod:`repoze.bfg` comes with a variety of implementations of these
+policies. To provide maximal flexibility, :mod:`repoze.bfg` also
+allows you to create custom authentication policies and authorization
+policies.
.. warning::
@@ -35,19 +59,30 @@ Authorization is enabled by modifying your application to include a
.. index::
pair: enabling; authorization policy
-Enabling an Authorization Policy Imperatively
----------------------------------------------
+Enabling an Authorization Policy
+--------------------------------
By default, :mod:`repoze.bfg` enables no authorization policy. All
-views are accessible by completely anonymous users.
+views are accessible by completely anonymous users. In order to begin
+protecting views from execution based on security settings, you need
+to enable an authorization policy.
+
+You can enable an authorization policy imperatively, or declaratively
+via ZCML.
-However, if you use the ``authorization_policy`` argument to the
-constructor of the :class:`repoze.bfg.configuration.Configurator`
-class, you can enable an authorization policy.
+.. index::
+ triple: enabling; authorization policy; imperatively
+
+Enabling an Authorization Policy Imperatively
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Passing an ``authorization_policy`` argument to the constructor of the
+:class:`repoze.bfg.configuration.Configurator` class enables an
+authorization policy.
You must also enable an :term:`authentication policy` in order to
-enable the an authorization policy (this is because authorization, in
-general, depends upon authentication). Use the
+enable the an authorization policy. This is because authorization, in
+general, depends upon authentication. Use the
``authorization_policy`` argument to the
:class:`repoze.bfg.configuration.Configurator` class during
application setup to specify an authentication policy.
@@ -81,20 +116,16 @@ See also the :mod:`repoze.bfg.authorization` and
:mod:`repoze.bfg.authentication` modules for alternate implementations
of authorization and authentication policies.
-It is also possible to construct your own custom authentication policy
-or authorization policy: see :ref:`creating_an_authentication_policy`
-and :ref:`creating_an_authorization_policy`.
-
.. index::
triple: enabling; authorization policy; via ZCML
Enabling an Authorization Policy Via ZCML
------------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you'd rather use :term:`ZCML` to specify an authorization policy
than imperative configuration, modify the ZCML file loaded by your
application (usually named ``configure.zcml``) to enable an
-authorization one.
+authorization policy.
For example, to enable a policy which compares the value of an "auth
ticket" cookie passed in the request's environment which contains a
@@ -136,10 +167,15 @@ authentication policy ZCML directives that should prove useful. See
Protecting Views with Permissions
---------------------------------
-You declaratively protect a particular view using a :term:`permission`
-name via the ``configure.zcml`` application registry. For example,
-the following declaration protects the view named ``add_entry.html``
-when invoked against a ``Blog`` context with the ``add`` permission:
+To protect a :term:`view callable` from invocation based on a user's
+security settings in a :term:`context`, you must pass a
+:term:`permission` to :term:`view configuration`. Permissions are
+usually just strings, and they have no required composition: you can
+name permissions whatever you like.
+
+For example, the following declaration protects the view named
+``add_entry.html`` when invoked against a ``Blog`` context with the
+``add`` permission:
.. code-block:: xml
:linenos:
@@ -151,9 +187,8 @@ when invoked against a ``Blog`` context with the ``add`` permission:
permission="add"
/>
-The equivalent view registration including the 'add' permission name
-may be performed via the ``bfg_view`` decorator within the "views"
-module of your project's package
+The equivalent view registration including the ``add`` permission name
+may be performed via the ``@bfg_view`` decorator:
.. ignore-next-block
.. code-block:: python
@@ -167,30 +202,40 @@ module of your project's package
""" Add blog entry code goes here """
pass
-If an authorization policy is in place when this view is found during
-normal application operations, the user will need to possess the
-``add`` permission against the context to be able to invoke the
-``blog_entry_add_view`` view.
+Or an the same thing can be done using the
+:meth:`repoze.bfg.configuration.Configurator.add_view` method:
+
+.. ignore-next-block
+.. code-block:: python
+ :linenos:
+
+ config.add_view(blog_entry_add_view,
+ context=Blog, name='add_entry.html', permission='add')
-Permission names are usually just strings. They hold no special
-significance to the system. You can name permissions whatever you
-like.
+As a result of any of these various view configuration statements, if
+an authorization policy is in place when the view callable is found
+during normal application operations, the requesting user will need to
+possess the ``add`` permission against the :term:`context` to be able
+to invoke the ``blog_entry_add_view`` view. If he does not, the
+:term:`Forbidden view` will be invoked.
.. index::
- pair: assigning; ACL
+ single: ACL
+ single: access control list
.. _assigning_acls:
Assigning ACLs to your Model Objects
------------------------------------
-When :mod:`repoze.bfg` determines whether a user possesses a
-particular permission in a :term:`context`, it examines the
-:term:`ACL` associated with the context. An ACL is associated with a
-context by virtue of the ``__acl__`` attribute of the model object
-representing the context. This attribute can be defined on the model
-*instance* (if you need instance-level security), or it can be defined
-on the model *class* (if you just need type-level security).
+When the default :mod:`repoze.bfg` :term:`authorization policy`
+determines whether a user possesses a particular permission in a
+:term:`context`, it examines the :term:`ACL` associated with the
+context. An ACL is associated with a context by virtue of the
+``__acl__`` attribute of the model object representing the
+:term:`context`. This attribute can be defined on the model
+*instance* if you need instance-level security, or it can be defined
+on the model *class* if you just need type-level security.
For example, an ACL might be attached to model for a blog via its
class:
@@ -236,6 +281,7 @@ access is required on an object-by-object basis.
.. index::
single: ACE
+ single: access control entry
Elements of an ACL
------------------
@@ -254,27 +300,22 @@ Here's an example ACL:
(Allow, 'group:editors', 'edit'),
]
-The example ACL indicates that the ``Everyone`` principal (a special
-system-defined principal indicating, literally, everyone) is allowed
+The example ACL indicates that the
+:data:`repoze.bfg.security.Everyone` principal -- a special
+system-defined principal indicating, literally, everyone -- is allowed
to view the blog, the ``group:editors`` principal is allowed to add to
and edit the blog.
-The third argument in an ACE can also be a sequence of permission
-names instead of a single permission name. So instead of creating
-multiple ACEs representing a number of different permission grants to
-a single ``group:editors`` group, we can collapse this into a single
-ACE, as below.
-
-.. code-block:: python
- :linenos:
-
- from repoze.bfg.security import Everyone
- from repoze.bfg.security import Allow
+Each elements of an ACL is an :term:`ACE` or access control entry.
+For example, in the above code block, there are three ACEs: ``(Allow,
+Everyone, 'view')``, ``(Allow, 'group:editors', 'add')``, and
+``(Allow, 'group:editors', 'edit')``.
- __acl__ = [
- (Allow, Everyone, 'view'),
- (Allow, 'group:editors', ('add', 'edit')),
- ]
+The first element of any ACE is either
+:data:`repoze.bfg.security.Allow`, or
+:data:`repoze.bfg.security.Deny`, representing the action to take when
+the ACE matches. The second element is a :term:`principal`. The
+third argument is a permission or sequence of permission names.
A principal is usually a user id, however it also may be a group id if
your authentication system provides group information and the
@@ -286,11 +327,8 @@ respects group information if you configure it with a ``callback``.
See :ref:`authentication_policies_directives_section` for more
information about the ``callback`` attribute.
-Each tuple within an ACL structure is known as a :term:`ACE`, which
-stands for "access control entry". For example, in the above ACL,
-``(Allow, Everyone, 'view')`` is an ACE. Each ACE in an ACL is
-processed by an authorization policy *in the order dictated by the
-ACL*. So if you have an ACL like this:
+Each ACE in an ACL is processed by an authorization policy *in the
+order dictated by the ACL*. So if you have an ACL like this:
.. code-block:: python
:linenos:
@@ -304,9 +342,10 @@ ACL*. So if you have an ACL like this:
(Deny, Everyone, 'view'),
]
-The authorization policy will *allow* everyone the view permission,
-even though later in the ACL you have an ACE that denies everyone the
-view permission. On the other hand, if you have an ACL like this:
+The default authorization policy will *allow* everyone the view
+permission, even though later in the ACL you have an ACE that denies
+everyone the view permission. On the other hand, if you have an ACL
+like this:
.. code-block:: python
:linenos:
@@ -320,9 +359,27 @@ view permission. On the other hand, if you have an ACL like this:
(Allow, Everyone, 'view'),
]
-The authorization policy will deny Everyone the view permission, even
+The authorization policy will deny everyone the view permission, even
though later in the ACL is an ACE that allows everyone.
+The third argument in an ACE can also be a sequence of permission
+names instead of a single permission name. So instead of creating
+multiple ACEs representing a number of different permission grants to
+a single ``group:editors`` group, we can collapse this into a single
+ACE, as below.
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.security import Everyone
+ from repoze.bfg.security import Allow
+
+ __acl__ = [
+ (Allow, Everyone, 'view'),
+ (Allow, 'group:editors', ('add', 'edit')),
+ ]
+
+
.. index::
single: principal
pair: special; principal names
@@ -364,7 +421,7 @@ module. These can be imported for use in ACLs.
An object representing, literally, *all* permissions. Useful in an
ACL like so: ``(Allow, 'fred', ALL_PERMISSIONS)``. The
``ALL_PERMISSIONS`` object is actually a stand-in object that has a
- ``__contains__`` method that always returns True, which, for all
+ ``__contains__`` method that always returns ``True``, which, for all
known authorization policies, has the effect of indicating that a
given principal "has" any permission asked for by the system.
@@ -374,16 +431,9 @@ module. These can be imported for use in ACLs.
Special ACEs
------------
-A convenience :term:`ACE` is defined within the
-:mod:`repoze.bfg.security` module named
-:data:`repoze.bfg.security.DENY_ALL`. It equals the following:
-
-.. code-block:: python
-
- from repoze.bfg.security import ALL_PERMISSIONS
- (Deny, Everyone, ALL_PERMISSIONS)
-
-This ACE is often used as the *last* ACE of an ACL to explicitly cause
+A convenience :term:`ACE` is defined representing a deny to everyone
+of all permissions in :data:`repoze.bfg.security.DENY_ALL`. This ACE
+is often used as the *last* ACE of an ACL to explicitly cause
inheriting authorization policies to "stop looking up the traversal
tree" (effectively breaking any inheritance). For example, an ACL
which allows *only* ``fred`` the view permission in a particular
@@ -398,11 +448,20 @@ authorization policy is in effect might look like so:
__acl__ = [ (Allow, 'fred', 'view'), DENY_ALL ]
+"Under the hood", the :data:`repoze.bfg.security.DENY_ALL` ACE equals
+the following:
+
+.. code-block:: python
+
+ from repoze.bfg.security import ALL_PERMISSIONS
+ (Deny, Everyone, ALL_PERMISSIONS)
+
.. index::
single: ACL inheritance
+ pair: location-aware; security
-ACL Inheritance
----------------
+ACL Inheritance and Location-Awareness
+--------------------------------------
While the default :term:`authorization policy` is in place, if a model
object does not have an ACL when it is the context, its *parent* is
@@ -410,12 +469,6 @@ consulted for an ACL. If that object does not have an ACL, *its*
parent is consulted for an ACL, ad infinitum, until we've reached the
root and there are no more parents left.
-.. index::
- pair: location-aware; security
-
-Location-Awareness
-------------------
-
In order to allow the security machinery to perform ACL inheritance,
model objects must provide :term:`location` -awareness. Providing
*location-awareness* means two things: the root object in the graph
diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst
index 960e2aa1d..6a83d1aba 100644
--- a/docs/narr/startup.rst
+++ b/docs/narr/startup.rst
@@ -30,19 +30,6 @@ file to infer settings and starts a server listening on a port. For
the purposes of this discussion, we'll assume that you are using this
command to run your :mod:`repoze.bfg` application.
-.. sidebar:: Using :mod:`repoze.bfg` Without ``paster``
-
- ``paster serve`` is by no means the only way to start up and serve
- a :mod:`repoze.bfg` application. Any :term:`WSGI` server is
- capable of running a :mod:`repoze.bfg` application, and some WSGI
- servers (such as :term:`mod_wsgi`) don't require the
- :term:`PasteDeploy` framework's ``paster serve`` command to do
- server process management. Each :term:`WSGI` server has its own
- documentation about how it creates a process to run an application,
- and there are many of them, so we cannot provide the details for
- each here. But the concepts are largely the same, whatever server
- you happen to use.
-
Here's a high-level time-ordered overview of what happens when you
press ``return`` after running ``paster serve MyProject.ini``.
@@ -53,16 +40,20 @@ press ``return`` after running ``paster serve MyProject.ini``.
information contained within the ``MyProject.ini`` file.
#. The PasteDeploy framework finds a section named either
- ``[app:main]`` or ``[pipeline:main]`` in the ``.ini`` file. This
- section represents the configuration of a :term:`WSGI` application
- that will be served. If you're using a simple application (e.g. an
- ``[app:main]`` section of a default-generated :mod:`repoze.bfg`
- project), the application :term:`entry point` or :term:`dotted
- Python name` will be named on the ``use=`` line within the
- section's configuration. If, instead of a simple application,
- you're using a WSGI :term:`pipeline` (e.g. a ``[pipeline:main]``
- section), the application named on the "last" element will refer to
- your :mod:`repoze.bfg` application.
+ ``[app:main]``, ``[pipeline:main]``, or ``[composite::main]`` in
+ the ``.ini`` file. This section represents the configuration of a
+ :term:`WSGI` application that will be served. If you're using a
+ simple application (e.g. an ``[app:main]`` section of a
+ default-generated :mod:`repoze.bfg` project), the application
+ :term:`entry point` or :term:`dotted Python name` will be named on
+ the ``use=`` line within the section's configuration. If, instead
+ of a simple application, you're using a WSGI :term:`pipeline`
+ (e.g. a ``[pipeline:main]`` section), the application named on the
+ "last" element will refer to your :mod:`repoze.bfg` application.
+ If instead of a simple application or a pipeline, you're using a
+ Paste "composite" (e.g. ``[composite:main]``), refer to the
+ documentation for that particular composite to understand how to
+ make it refer to your :mod:`repoze.bfg` application.
#. The application's *constructor* (named by the entry point reference
or dotted Python name on the ``use=`` line) is passed the key/value
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index f6f4098e0..4f5124a59 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -2,134 +2,277 @@ Templates
=========
A :term:`template` is a file on disk which can be used to render
-dynamic data provided by a :term:`view`, usually surrounded by
-information that is static. :mod:`repoze.bfg` offers a number of ways
-to perform templating tasks "out of the box", and provides alternative
-templating language support via add-on "bindings" packages.
+dynamic data provided by a :term:`view`. :mod:`repoze.bfg` offers a
+number of ways to perform templating tasks out of the box, and
+provides add-on templating support through a set of bindings packages.
-.. index::
- triple: Chameleon; ZPT; templates
+Out of the box, :mod:`repoze.bfg` provides templating via the
+:term:`Chameleon` templating library. :term:`Chameleon` provides
+support for two different types of templates: :term:`ZPT` templates
+and text templates.
-.. _chameleon_zpt_templates:
+Before discussing how built-in templates are templates are used in
+detail, we'll discuss two ways to render templates within
+:mod:`repoze.bfg` in general: directly, and via renderer
+configuration.
-Templating With :term:`Chameleon` ZPT Page Templates
-----------------------------------------------------
+.. index::
+ single: templates used directly
+ single: Mako
-Like :term:`Zope`, :mod:`repoze.bfg` uses Zope Page Templates
-(:term:`ZPT`) as its default and best-supported templating
-language. However, :mod:`repoze.bfg` uses a different implementation
-of the :term:`ZPT` specification than Zope does: the :term:`Chameleon`
-templating engine. This templating engine complies largely with the
-`Zope Page Template <http://wiki.zope.org/ZPT/FrontPage>`_ template
-specification, however it is significantly faster.
+.. _templates_used_directly:
-.. note:: The language definition documentation for Chameleon
- ZPT-style templates is available from `the Chameleon website
- <http://chameleon.repoze.org>`_. See its `documentation
- <http://chameleon.repoze.org/docs/latest/>`_ for the Chameleon ZPT
- language specification.
+Templates Used Directly
+-----------------------
-Given that there is a :term:`Chameleon` ZPT template named ``foo.pt``
+The most straightforward way to use a template within
+:mod:`repoze.bfg` is to cause it to be rendered directly within a
+:term:`view callable`. You may use whatever API is supplied by a
+given templating engine to do so.
+
+:mod:`repoze.bfg` provides various APIs that allow you to render
+:term:`Chameleon` templates directly from within a view callable. For
+example, if there is a :term:`Chameleon` ZPT template named ``foo.pt``
in a directory in your application named ``templates``, you can render
-the template from a view like so:
+the template from within the body of view callable like so:
.. code-block:: python
:linenos:
from repoze.bfg.chameleon_zpt import render_template_to_response
+
def sample_view(request):
return render_template_to_response('templates/foo.pt', foo=1, bar=2)
-The first argument to
-:func:`repoze.bfg.chameleon_zpt.render_template_to_response` shown
-above (and its sister function
-:func:`repoze.bfg.chameleon_zpt.render_template`, not shown, which
-just returns a string body) is the template *path*. In the example
-above, the path ``templates/foo.pt`` is *relative*. Relative to what,
-you ask? Relative to the directory in which the ``views.py`` file
-which names it lives, which is usually the :mod:`repoze.bfg`
-application's :term:`package` directory.
-
-Although a path is usually just a simple relative pathname, a path
-passed to :func:`repoze.bfg.chameleon_zpt.render_template_to_response`
-can be absolute, starting with a slash on UNIX or a drive letter
-prefix on Windows. The path can alternately be a :term:`resource
-specification` in the form ``some.dotted.package_name:relative/path``,
-making it possible to address template resources which live in another
-package.
-
-:func:`repoze.bfg.chameleon_zpt.render_template_to_response` always
-returns a :term:`Response` object which has a *status code* of ``200
-OK`` and a *content-type* of ``text-html``. If you need more control
-over the status code and content-type, either set attributes on the
-response that this function returns or use the ``render_template``
-function instead (see :ref:`chameleon_zpt_module` for the details),
-which also renders a ZPT template but returns a string instead of a
-Response. You can use the string manually as a response body. Here's
-an example of using :func:`repoze.bfg.chameleon_zpt.render_template`:
+The ``sample_view`` :term:`view callable` above returns a
+:term:`response` object which contains the body of the
+``template/foo.pt`` template. The template author will have the names
+``foo`` and ``bar`` available as top-level names for replacement or
+comparison purposes.
+
+Every views must return a :term:`response` object (except for views
+which use a :term:`renderer`, which we'll see shortly). The
+:func:`repoze.bfg.chameleon_zpt.render_template_to_response` function
+is a shortcut function that actually returns a response object, but
+not all template APIs know about responses. When you use an template
+API that is "response-ignorant" you can also easily render a template
+to a string, and construct your own response object as necessary with
+the string as the body.
+
+For example, the :func:`repoze.bfg.chameleon_zpt.render_template` API
+returns a string. We can manufacture a :term:`response` object
+directly, and use that string as the body of the response:
.. code-block:: python
:linenos:
from repoze.bfg.chameleon_zpt import render_template
from webob import Response
+
def sample_view(request):
result = render_template('templates/foo.pt', foo=1, bar=2)
response = Response(result)
- response.content_type = 'text/plain'
return response
-Here's an example of using
-:func:`repoze.bfg.chameleon_zpt.render_template_to_response` but
-changing the content-type and status:
+Because :term:`view callable` functions are typically the only code in
+:mod:`repoze.bfg` that need to know anything about templates, and
+because view functions are very simple Python, you can use whatever
+templating system you're most comfortable with within
+:mod:`repoze.bfg`. Install the templating system, import its API
+functions into your views module, use those APIs to generate a string,
+then return that string as the body of a :term:`WebOb`
+:term:`Response` object.
+
+For example, here's an example of using `Mako
+<http://www.makotemplates.org/>`_ from within a :mod:`repoze.bfg`
+:term:`view`:
+
+.. ignore-next-block
+.. code-block:: python
+ :linenos:
+
+ from mako.template import Template
+ from webob import Response
+
+ def make_view(request):
+ template = Template(filename='/templates/template.mak')
+ result = template.render(name=request.params['name'])
+ response = Response(result)
+ return response
+
+.. note::
+
+ If you use third-party templating languages without cooperating BFG
+ bindings directly within view callables, the auto-template-reload
+ strategy explained in :ref:`reload_templates_section` will not be
+ available, nor will the template resource overriding capability
+ explained in :ref:`overriding_resources_section` be available, nor
+ will it be possible to use any template using that language as a
+ :term:`renderer`. However, it's reasonably easy to write custom
+ templating system binding packages for use under :mod:`repoze.bfg`
+ so that templates written in the language can be used as renderers.
+ See :ref:`available_template_system_bindings` for example packages.
+
+If you need more control over the status code and content-type, or
+other response attributes from views that use direct templating, you
+may set attributes on the response that influence these values.
+
+Here's an example of changing the content-type and status of the
+response object returned by
+:func:`repoze.bfg.chameleon_zpt.render_template_to_response`:
.. code-block:: python
:linenos:
from repoze.bfg.chameleon_zpt import render_template_to_response
+
def sample_view(request):
response = render_template_to_response('templates/foo.pt', foo=1, bar=2)
response.content_type = 'text/plain'
response.status_int = 204
return response
+Here's an example of manufacturing a response object using the result of
+:func:`repoze.bfg.chameleon_zpt.render_template` (a string):
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.chameleon_zpt import render_template
+ from webob import Response
+ def sample_view(request):
+ result = render_template('templates/foo.pt', foo=1, bar=2)
+ response = Response(result)
+ response.content_type = 'text/plain'
+ return response
+
.. index::
single: templates used as renderers
pair: renderers; template
+.. _templates_used_as_renderers:
+
Templates Used as Renderers
---------------------------
-Instead of using the various ``render_template_*`` APIs directly
-within a view function to render a specific template, you can
-associate a template (at least one written in a built-in templating
-language) with a view indirectly by specifying it as a
-:term:`renderer`.
-
-To do so, return a *dictionary* from the view code, and specify the
-template :term:`resource specification` as the ``renderer`` argument
-or attribute to the :term:`view configuration` of the view you're
-trying to render using that template. The items returned by the view
-in the dictionary will be made available to the template as top-level
+Instead of using templating system APIs within a the body of a view
+function directly to render a specific template, you may associate a
+template written in a supported templating language with a view
+indirectly by specifying it as a :term:`renderer`.
+
+To use a renderer, specify a template :term:`resource specification`
+as the ``renderer`` argument or attribute to the :term:`view
+configuration` of a :term:`view callable`. Then return a *dictionary*
+from that view callable. The dictionary items returned by the view
+callable will be made available to the renderer template as top-level
names.
The association of a template as a renderer for a :term:`view
configuration` makes it possible to replace code within a :term:`view
-callable` that handles the rendering of a template. For example, we
-can replace the call to
-:func:`repoze.bfg.chameleon_zpt.render_template_to_response` in the
-below view callable.
+callable` that handles the rendering of a template.
+
+Here's an example of using a :class:`repoze.bfg.view.bfg_view`
+decorator to specify a :term:`view configuration` that names a
+template renderer:
.. code-block:: python
:linenos:
- from repoze.bfg.chameleon_zpt import render_template_to_response
+ from repoze.bfg.view import bfg_view
+
+ @bfg_view(renderer='templates/foo.pt')
def my_view(request):
- return render_template_to_response('templates/foo.pt', foo=1, bar=2)
+ return {'foo':1, 'bar':2}
-Instead, using a :class:`repoze.bfg.view.bfg_view` decorator to
-specify a :term:`view configuration`, a template renderer for the view
-can be specified like so:
+The ``renderer`` argument to the ``@bfg_view`` configuration decorator
+shown above is the template *path*. In the example above, the path
+``templates/foo.pt`` is *relative*. Relative to what, you ask?
+Relative to the directory in which the file which defines the view
+configuration lives. In this case, this is the directory containing
+the file that defines the ``my_view`` function.
+
+Although a renderer path is usually just a simple relative pathname, a
+path named as a renderer can be absolute, starting with a slash on
+UNIX or a drive letter prefix on Windows. The path can alternately be
+a :term:`resource specification` in the form
+``some.dotted.package_name:relative/path``, making it possible to
+address template resources which live in another package.
+
+When a template :term:`renderer` is used to render the result of a
+view callable, several names are passed into the template as top-level
+names by default, including ``context`` and ``request``. Similar
+renderer configuration can be done imperatively and via :term:`ZCML`.
+See :ref:`views_which_use_a_renderer`. See also
+:ref:`built_in_renderers`.
+
+Not just any template from any arbitrary templating systemmay be used
+as a renderer. Bindings must exist specifically for :mod:`repoze.bfg`
+to use a templating language template as a renderer. Currently,
+:mod:`repoze.bfg` has built-in support for two Chameleon templating
+languages: ZPT and text. See :ref:`built_in_renderers` for a
+discussion of their details. :mod:`repoze.bfg` also supports the use
+of :term:`Jinja2` templates as renderers. See
+:ref:`available_template_system_bindings`.
+
+.. sidebar:: Why Use A Renderer
+
+ Using a renderer is usually a better way to render templates than
+ using any templating API directly from within a :term:`view
+ callable` because it makes the view callable more unit-testable.
+ Views which use templating APIs directly must return a
+ :term:`Response` object. Making testing assertions about response
+ objects is typically an indirect process, because it means that
+ your test code often needs to somehow needs to parse information
+ out of the response body (often HTML). View callables which use
+ renderers typically return a dictionary, and making assertions
+ about the information is almost always more direct than needing to
+ parse HTML.
+
+ Specifying a renderer from within :term:`ZCML` (as opposed to
+ imperatively or via a ``bfg_view`` decorator, or using a template
+ directly from within a view callable) also makes it possible for
+ someone to modify the template used to render a view without
+ needing to fork your code to do so. See :ref:`extending_chapter`
+ for more information.
+
+By default, views rendered via a template renderer return a
+:term:`Response` object which has a *status code* of ``200 OK`` and a
+*content-type* of ``text/html``. To vary attributes of the response
+of a view that uses a renderer, such as the content-type, headers, or
+status attributes, you must set attributes on the *request* object
+within the view before returning the dictionary. See
+:ref:`response_request_attrs` for more information.
+
+.. index::
+ triple: Chameleon; ZPT; templates
+
+.. _chameleon_zpt_templates:
+
+:term:`Chameleon` ZPT Templates
+-------------------------------
+
+Like :term:`Zope`, :mod:`repoze.bfg` uses :term:`ZPT` (Zope Page
+Templates) as its default templating language. However,
+:mod:`repoze.bfg` uses a different implementation of the :term:`ZPT`
+specification than Zope does: the :term:`Chameleon` templating
+engine. The Chameleon engine complies largely with the `Zope Page
+Template <http://wiki.zope.org/ZPT/FrontPage>`_ template
+specification. However, it is significantly faster.
+
+The language definition documentation for Chameleon ZPT-style
+templates is available from `the Chameleon website
+<http://chameleon.repoze.org/>`_.
+
+.. warning::
+
+ :term:`Chameleon` only works on :term:`CPython` platforms. This
+ does not include Google's App Engine nor :term:`Jython`. On these
+ platforms, you should use ``repoze.bfg.jinja2`` instead. See
+ :ref:`available_template_system_bindings`.
+
+Given that there is a :term:`Chameleon` ZPT template named ``foo.pt``
+in a directory in your application named ``templates``, you can render
+the template as a :term:`renderer` like so:
.. code-block:: python
:linenos:
@@ -140,19 +283,18 @@ can be specified like so:
def my_view(request):
return {'foo':1, 'bar':2}
-Unlike when the various ``render_template_*`` APIs are used, when a
-template :term:`renderer` is used to render the result of a view
-callable, several names are passed into the template as top-level
-names by default, including ``context`` and ``request``. Similar
-renderer configuration can be done imperatively and via :term:`ZCML`.
-See :ref:`views_which_use_a_renderer`. See also
-:ref:`built_in_renderers`.
+If you'd rather use templates directly within a view callable (without
+the indirection of using a renderer), see :ref:`chameleon_zpt_module`
+for the API description.
+
+See also :ref:`built_in_renderers` for more general information about
+renderers, including Chameleon ZPT renderers.
.. index::
single: sample template
-A Sample Template
------------------
+A Sample ZPT Template
+~~~~~~~~~~~~~~~~~~~~~
Here's what a simple :term:`Chameleon` ZPT template used under
:mod:`repoze.bfg` might look like:
@@ -189,25 +331,24 @@ works in these templates.
single: ZPT macros
Using ZPT Macros in :mod:`repoze.bfg`
--------------------------------------
-
-Unlike Zope "browser views", :mod:`repoze.bfg` doesn't make any names
-such as ``context`` or ``view`` available to :term:`Chameleon` ZPT
-templates by default unless a :term:`renderer` is used. Instead, it
-expects you to pass all the names you need into the template.
-
-One of the common needs in ZPT-based template is to one template's
-"macros" from within a different template. In Zope, this is typically
-handled by retrieving the template from the ``context``. To do the
-same thing in :mod:`repoze.bfg`, you need to make the macro template
-itself available to the rendered template by passing template in which
-the macro is defined (or even the macro itself) *into* the rendered
-template. To make a macro available to the rendered template, you can
-retrieve a different template using the
-:func:`repoze.bfg.chameleon_zpt.get_template` API, and pass it in to
-the template being rendered. For example, using a :term:`view
-configuration` via a :class:`repoze.bfg.view.bfg_view` decorator that
-uses a :term:`renderer`:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When a :term:`renderer` is used to render a template,
+:mod:`repoze.bfg` makes at least two top-level names available to the
+template by default: ``context`` and ``request``. One of the common
+needs in ZPT-based template is to one template's "macros" from within
+a different template. In Zope, this is typically handled by
+retrieving the template from the ``context``. But having a hold of
+the context in :mod:`repoze.bfg` is not helpful: templates cannot
+usually be retrieved from models. To use macros in :mod:`repoze.bfg`,
+you need to make the macro template itself available to the rendered
+template by passing template in which the macro is defined (or even
+the macro itself) *into* the rendered template. To make a macro
+available to the rendered template, you can retrieve a different
+template using the :func:`repoze.bfg.chameleon_zpt.get_template` API,
+and pass it in to the template being rendered. For example, using a
+:term:`view configuration` via a :class:`repoze.bfg.view.bfg_view`
+decorator that uses a :term:`renderer`:
.. code-block:: python
:linenos:
@@ -262,8 +403,8 @@ you can create templates that are entirely composed of text except for
``${name}`` -style substitution points.
Here's an example usage of a Chameleon text template. Create a file
-on disk named ``text.txt`` in your project's ``templates`` directory
-with the following contents::
+on disk named ``mytemplate.txt`` in your project's ``templates``
+directory with the following contents::
Hello, ${name}!
@@ -273,17 +414,25 @@ which renders this template:
.. code-block:: python
:linenos:
- from repoze.bfg.chameleon_text import render_template_to_response
+ from repoze.bfg.chameleon_zpt import get_template
+ from repoze.bfg.view import bfg_view
+
+ @bfg_view(renderer='templates/mytemplate.txt')
+ def my_view(request):
+ return {'name':'world'}
+
+When the template is renderered, it will show:
+
+.. code-block:: text
- def text_view(request):
- return render_template_to_response('templates/text.txt', name='World')
+ Hello, world!
-The Chameleon text rendering API is a wholesale mirror of the
-Chameleon text ZPT rendering API, it's just imported from another
-place; see :ref:`chameleon_text_module` for the API description.
+If you'd rather use templates directly within a view callable (without
+the indirection of using a renderer), see :ref:`chameleon_text_module`
+for the API description.
-A Chameleon text template can also be used as a :term:`renderer`. See
-:ref:`built_in_renderers` for more information.
+See also :ref:`built_in_renderers` for more general information about
+renderers, including Chameleon text renderers.
.. index::
pair: template renderer; side effects
@@ -294,13 +443,9 @@ Side Effects of Rendering a Chameleon Template
When a Chameleon template is rendered from a file, the templating
engine writes a file in the same directory as the template file itself
as a kind of cache, in order to do less work the next time the
-template needs to be read from disk. When using ``chameleon.core``
-version 1.0b32 and lower, this filename is ``<template_name>.cache``.
-When using ``chameleon.core`` version 1.0b33 and higher or the
-``Chameleon`` (uppercase-C) package, this filename is
-``<template_name>.py``. If you see "strange" ``.py`` or ``.cache``
-files showing up in your ``templates`` directory, it is due to this
-feature.
+template needs to be read from disk. If you see "strange" ``.py``
+files showing up in your ``templates`` directory (or otherwise
+directly "next" to your templates), it is due to this feature.
If you're using a version control system such as Subversion, you
should cause it to ignore these files. Here's the contents of my
@@ -311,8 +456,8 @@ should cause it to ignore these files. Here's the contents of my
.. code-block:: bash
:linenos:
- *.cache
*.pt.py
+ *.txt.py
.. index::
pair: template; automatic reloading
@@ -361,48 +506,6 @@ documentation for information about supporting internationalized units
of text within :term:`Chameleon` templates.
.. index::
- single: other templating languages
-
-Templating with other Templating Languages
-------------------------------------------
-
-Because :term:`view callable` functions are typically the only code in
-:mod:`repoze.bfg` that need to know anything about templates, and
-because view functions are very simple Python, you can use whatever
-templating system you're most comfortable with within
-:mod:`repoze.bfg`. Install the templating system, import its API
-functions into your views module, use those APIs to generate a string,
-then return that string as the body of a :term:`WebOb`
-:term:`Response` object. Assuming you have `Mako
-<http://www.makotemplates.org/>`_ installed, here's an example of
-using Mako from within a :mod:`repoze.bfg` :term:`view`:
-
-.. ignore-next-block
-.. code-block:: python
- :linenos:
-
- from mako.template import Template
- from webob import Response
-
- def mako_view(request):
- template = Template(filename='/templates/template.mak')
- result = template.render(name=request.params['name'])
- response = Response(result)
- return response
-
-.. note:: It's reasonably easy to write custom templating system
- binding packages for use under :mod:`repoze.bfg`. See
- :ref:`available_template_system_bindings` for example packages.
-
-Note that if you use third-party templating languages without
-cooperating BFG bindings, the auto-template-reload strategy explained
-in :ref:`reload_templates_section` will not be available, nor will the
-template resource overriding capability explained in
-:ref:`overriding_resources_section` be available, nor will it be
-possible to use any template using that language as a
-:term:`renderer`.
-
-.. index::
single: template system bindings
single: Jinja2
single: Genshi
@@ -412,19 +515,8 @@ possible to use any template using that language as a
Available Add-On Template System Bindings
-----------------------------------------
-:mod:`repoze.bfg.xslt` is an add-on which provides XSL template
-bindings. It lives in the Repoze Subversion repository at
-`http://svn.repoze.org/repoze.bfg.xslt
-<http://svn.repoze.org/repoze.bfg.xslt>`_.
-
-:mod:`repoze.bfg.chameleon_genshi` is an add-on which provides
-Chameleon Genshi-style template support. It lives in the Repoze
-Subversion repository at
-`http://svn.repoze.org/repoze.bfg.chameleon_genshi
-<http://svn.repoze.org/repoze.bfg.chameleon_genshi>`_.
-
Jinja2 template bindings are available for :mod:`repoze.bfg` in the
-:mod:`repoze.bfg.jinja2` package. It lives in the Repoze Subversion
+``repoze.bfg.jinja2`` package. It lives in the Repoze Subversion
repository at `http://svn.repoze.org/repoze.bfg.jinja2
<http://svn.repoze.org/repoze.bfg.jinja2>`_; it is also available from
:term:`PyPI`.
diff --git a/docs/narr/unittesting.rst b/docs/narr/unittesting.rst
index b6b8a67e6..9e36ea051 100644
--- a/docs/narr/unittesting.rst
+++ b/docs/narr/unittesting.rst
@@ -7,25 +7,27 @@
Unit and Integration Testing
============================
-*Unit testing* is the act of testing a "unit" in your application. In
-this context, a "unit" is often a function or a method of a class
-instance. The unit is also referred to as a "unit under test". The
-goal of a single unit test is to test **only** some permutation of the
-"unit under test". If you write a unit test that aims to verify the
-result of a particular codepath through a Python function, you need
-only be concerned about testing the code that *lives in the function
-body itself*. If the function accepts a parameter that represents a
-complex application "domain object" (such a a model, a database
-connection, or an SMTP server), the argument provided to this function
-during a unit test *need not be* and likely *should not be* a "real"
-implementation object. For example, although a particular function
-implementation may accept an argument that represents an SMTP server
-object, and the function may call a method of this object when the
-system is operating normally that would result in an email being sent,
-a unit test of this codepath of the function does *not* need to test
-that an email is actually sent. It just needs to make sure that the
-function calls the method of the object provided as an argument that
-*would* send an email if the argument happened to be the "real"
+*Unit testing* is, not surprisingly, the act of testing a "unit" in
+your application. In this context, a "unit" is often a function or a
+method of a class instance. The unit is also referred to as a "unit
+under test".
+
+The goal of a single unit test is to test **only** some permutation of
+the "unit under test". If you write a unit test that aims to verify
+the result of a particular codepath through a Python function, you
+need only be concerned about testing the code that *lives in the
+function body itself*. If the function accepts a parameter that
+represents a complex application "domain object" (such a a model, a
+database connection, or an SMTP server), the argument provided to this
+function during a unit test *need not be* and likely *should not be* a
+"real" implementation object. For example, although a particular
+function implementation may accept an argument that represents an SMTP
+server object, and the function may call a method of this object when
+the system is operating normally that would result in an email being
+sent, a unit test of this codepath of the function does *not* need to
+test that an email is actually sent. It just needs to make sure that
+the function calls the method of the object provided as an argument
+that *would* send an email if the argument happened to be the "real"
implementation of an SMTP server object.
An *integration test*, on the other hand, is a different form of
@@ -149,8 +151,8 @@ What?
~~~~~
Thread local data structures are always a bit confusing, especially
-when used by frameworks. Sorry. So here's a rule of thumb: if you
-don't *know* whether you're calling code that uses the
+when they're used by frameworks. Sorry. So here's a rule of thumb:
+if you don't *know* whether you're calling code that uses the
:func:`repoze.bfg.threadlocal.get_current_registry` or
:func:`repoze.bfg.threadlocal.get_current_request` functions, or you
don't care about any of this, but you still want to write test code,
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index cc2a8a0c7..699d017c4 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -38,13 +38,13 @@ To allow for URL dispatch to be used, the :mod:`repoze.bfg` framework
allows you to inject ``route`` ZCML directives into your application's
``configure.zcml`` file.
-The mod:`repoze.bfg` :term:`Router` checks an incoming request against
-a *routes map* to find a :term:`context` and a :term:`view callable`
-before :term:`traversal` has a chance to find these things first. If
-a route matches, a :term:`context` is generated and :mod:`repoze.bfg`
-will call the :term:`view callable` found due to the context and the
-request. If no route matches, :mod:`repoze.bfg` will fail over to
-calling the :term:`root factory` callable passed to the
+The :mod:`repoze.bfg` :term:`Router` checks an incoming request
+against a *routes map* to find a :term:`context` and a :term:`view
+callable` before :term:`traversal` has a chance to find these things
+first. If a route matches, a :term:`context` is generated and
+:mod:`repoze.bfg` will call the :term:`view callable` found due to the
+context and the request. If no route matches, :mod:`repoze.bfg` will
+fail over to calling the :term:`root factory` callable passed to the
:term:`Configurator` for the application (usually a traversal
function).
@@ -59,8 +59,8 @@ error to the user's browser when no routes match.
.. index::
single: add_route
-The ``add_route`` Configurator Method
--------------------------------------
+Configuring a Route via The ``add_route`` Configurator Method
+-------------------------------------------------------------
The :meth:`repoze.bfg.configuration.Configurator.add_route` method
adds a single :term:`route configuration` to the :term:`application
@@ -95,6 +95,48 @@ documentation.
:term:`ZCML` will be used to perform route configuration.
.. index::
+ pair: route; ordering
+
+Route Ordering
+--------------
+
+ZCMl ``<route>`` declaration ordering and the ordering of calls to
+:mod:`repoze.bfg.configuration.Configurator.add_route` is very
+important, because routes are evaluated in a specific order. The
+order that routes are evaluated is the order in which they are added
+to the application at startup time. For ZCML, the order that routes
+are evaluated is the order in which they appear in the ZCML relative
+to each other.
+
+This is unlike traversal, which depends on emergent behavior rather
+than an ordered list of declarations.
+
+.. index::
+ pair: route; factory
+ single: route factory
+
+Route Factories
+---------------
+
+A "route" declaration can mention a "factory". When a factory is
+attached to a route, it is used to generate a root (it's a :term:`root
+factory`) instead of the *default* root factory. This object will be
+used as the :term:`context` of the view callable the route represents.
+
+.. code-block:: xml
+
+ <route
+ path="/abc"
+ name="abc"
+ view=".views.theview"
+ factory=".models.root_factory"
+ />
+
+In this way, each route can use a different factory, making it
+possible to supply a different :term:`context` object to the view
+related to each route.
+
+.. index::
pair: URL dispatch; matchdict
The Matchdict
diff --git a/docs/narr/urlmapping.rst b/docs/narr/urlmapping.rst
index 60bb09079..8d755bba3 100644
--- a/docs/narr/urlmapping.rst
+++ b/docs/narr/urlmapping.rst
@@ -7,23 +7,13 @@
Mapping URLs to Code
--------------------
-Many popular web frameworks today use :term:`URL dispatch` to
-associate a particular URL with a bit of code. In these systems, the
-bit of code associated with a URL is known somewhat ambiguously as a
-"controller" or :term:`view` depending upon the particular vocabulary
-religion to which you subscribe. Such systems allow the developer to
-create "urlconfs" or "routes" to controller/view Python code using
-pattern matching against URL components. Examples: `Django's URL
-dispatcher
-<http://www.djangoproject.com/documentation/url_dispatch/>`_ and the
-:term:`Routes` URL mapping system.
-
:mod:`repoze.bfg` supports :term:`URL dispatch` via a subsystem that
-was inspired by :term:`Routes`. :term:`URL dispatch` is convenient
-and straightforward. When you limit your application to using URL
-dispatch, you know every URL that your application might generate or
-respond to, and all the URL matching elements are listed in a single
-place.
+was inspired by the :term:`Routes` system used by :term:`Pylons`.
+:term:`URL dispatch` is convenient and straightforward: an incoming
+URL is checked against a list of potential matches in a predefined
+order. Each potential match often maps directly to a :term:`view
+callable`. When a match is found, it usually means that a particular
+:term:`view callable` is invoked.
Like :term:`Zope`, :mod:`repoze.bfg` can also map URLs to code
slightly differently, by using using object graph :term:`traversal`.
@@ -31,7 +21,8 @@ Graph-traversal based dispatching is useful if you like your URLs to
represent an arbitrary hierarchy of potentially heterogeneous items,
or if you need to attach "instance-level" security (akin to
"row-level" security in relational parlance) declarations to
-:term:`model` instances.
+:term:`model` instances. Traversal is slightly more complex than URL
+dispatch, but it is also a bit more powerful.
:term:`URL dispatch` can easily handle URLs such as
``http://example.com/members/Chris``, where it's assumed that each
@@ -50,7 +41,10 @@ In this configuration, there are exactly two types of URLs that will
match views in your application: ones that start with ``/archives``
and have subsequent path elements that represent a year, month, and
day. And ones that start with ``/members`` which are followed by a
-path segment containing a member's name. This is very simple.
+path segment containing a member's name. This is very simple. When
+you limit your application to using URL dispatch, you know every URL
+that your application might generate or respond to, and all the URL
+matching elements are listed in a single place.
:term:`URL dispatch` is not very good, however, at inferring the
difference between sets of URLs such as:
@@ -68,12 +62,12 @@ URL-dispatch based systems, and some assertions just aren't possible.
For example, URL-dispatch based systems don't deal very well with URLs
that represent arbitrary-depth hierarchies.
-Graph :term:`traversal` works well for these types of "ambiguous" URLs
-and for URLs that represent arbitrary-depth hierarchies. When
-traversal is used, each URL segment represents a single traversal step
-through an edge of a graph. So a URL like
-``http://example.com/a/b/c`` can be thought of as a graph traversal on
-the ``example.com`` site through the edges ``a``, ``b``, and ``c``.
+:term:`traversal` works well for these types of "ambiguous" URLs and
+for URLs that represent arbitrary-depth hierarchies. When traversal
+is used, each URL segment represents a single traversal step through
+an edge of a graph. So a URL like ``http://example.com/a/b/c`` can be
+thought of as a graph traversal on the ``example.com`` site through
+the edges ``a``, ``b``, and ``c``.
If you're willing to treat your application models as a graph that can
be traversed, it also becomes easy to provide "row-level security" (in
@@ -101,3 +95,4 @@ either as you see fit.
traversal
urldispatch
+ hybrid
diff --git a/docs/narr/vhosting.rst b/docs/narr/vhosting.rst
index 80336fe82..be42dc842 100644
--- a/docs/narr/vhosting.rst
+++ b/docs/narr/vhosting.rst
@@ -3,16 +3,24 @@
Virtual Hosting
===============
+"Virtual hosting" is, loosely, the act of serving a :mod:`repoze.bfg`
+application or a portion of a :mod:`repoze.bfg` application under a
+URL space that it does not "naturally" inhabit.
+
+:mod:`repoze.bfg` provides facilities for serving an application under
+a URL "prefix", as well as serving a *portion* of a :term:`traversal`
+based application under a root URL.
+
.. index::
pair: virtual hosting; URL prefix
Hosting an Application Under a URL Prefix
-----------------------------------------
-:mod:`repoze.bfg` supports a traditional form of virtual hosting
-whereby you can host a :mod:`repoze.bfg` application as a "subset" of
-some other site (e.g. under ``http://example.com/mybfgapplication/``
-as opposed to under ``http://example.com/``).
+:mod:`repoze.bfg` supports a common form of virtual hosting whereby
+you can host a :mod:`repoze.bfg` application as a "subset" of some
+other site (e.g. under ``http://example.com/mybfgapplication/`` as
+opposed to under ``http://example.com/``).
If you use a "pure Python" environment, this functionality is provided
by Paste's `urlmap <http://pythonpaste.org/modules/urlmap.html>`_
@@ -23,20 +31,10 @@ hosting translation for you "under the hood".
If you use the ``urlmap`` composite application "in front" of a
:mod:`repoze.bfg` application or if you use :term:`mod_wsgi` to serve
up a :mod:`repoze.bfg` application, nothing special needs to be done
-within the application for URLs to be generated that contain the
-prefix spelled by the package config. :mod:`paste.urlmap` and
-:term:`mod_wsgi` and manipulate the WSGI environment in such a way
-that the ``PATH_INFO`` and ``SCRIPT_NAME`` variables are correct for
-some given prefix.
-
-.. note:: If you're using an Apache server to proxy to a Paste
- ``urlmap`` composite, you may have to use the `ProxyPreserveHost
- <http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypreservehost>`_
- directive to pass the original ``HTTP_HOST`` header along to the
- application, so URLs get generated properly. As of this writing
- the ``urlmap`` composite does not seem to respect the
- ``HTTP_X_FORWARDED_HOST`` parameter, which will contain the
- original host header even if ``HTTP_HOST`` is incorrect.
+within the application for URLs to be generated that contain a
+prefix. :mod:`paste.urlmap` and :term:`mod_wsgi` and manipulate the
+:term:`WSGI` environment in such a way that the ``PATH_INFO`` and
+``SCRIPT_NAME`` variables are correct for some given prefix.
Here's an example of a PasteDeploy configuration snippet that includes
a ``urlmap`` composite.
@@ -54,9 +52,19 @@ This "roots" the :mod:`repoze.bfg` application at the prefix
``/bfgapp`` and serves up the composite as the "main" application in
the file.
+.. note:: If you're using an Apache server to proxy to a Paste
+ ``urlmap`` composite, you may have to use the `ProxyPreserveHost
+ <http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypreservehost>`_
+ directive to pass the original ``HTTP_HOST`` header along to the
+ application, so URLs get generated properly. As of this writing
+ the ``urlmap`` composite does not seem to respect the
+ ``HTTP_X_FORWARDED_HOST`` parameter, which will contain the
+ original host header even if ``HTTP_HOST`` is incorrect.
+
If you use :term:`mod_wsgi`, you do not need to use a ``composite``
-application in your .ini file. The ``WSGIScriptAlias`` configuration
-setting in a :term:`mod_wsgi` configuration does the work for you:
+application in your ``.ini`` file. The ``WSGIScriptAlias``
+configuration setting in a :term:`mod_wsgi` configuration does the
+work for you:
.. code-block:: apache
:linenos:
@@ -74,7 +82,7 @@ Virtual Root Support
:mod:`repoze.bfg` also supports "virtual roots", which can be used in
:term:`traversal` -based (but not :term:`URL dispatch` -based)
-applications. These are explained below.
+applications.
Virtual root support is useful when you'd like to host some model in a
:mod:`repoze.bfg` object graph as an application under a URL pathname
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index df3c92fc1..16a580221 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -3,40 +3,63 @@
Views
=====
-A :term:`view callable` is a callable which is invoked when a request
-enters your application. The primary job of any :mod:`repoze.bfg`
-application is is to find and call a :term:`view callable` when a
-:term:`request` reaches it. A :term:`view callable` is referred to in
-shorthand as a :term:`view`.
+Views do the "heavy lifting" within almost every :mod:`repoze.bfg`
+application. The primary job of any :mod:`repoze.bfg` application is
+is to find and call a :term:`view callable` when a :term:`request`
+reaches it. A :term:`view callable` is a callable which is invoked
+when a request enters your application.
-.. note:: See :ref:`traversal_intro` for an example of how a view
- might be found as the result of a request.
+A :term:`view callable` is mapped to one or more URLs by virtue of
+:term:`view configuration`. View configuration is performed in one of
+three ways:
-Most views accept a single argument named ``request``. This argument
+- by using the :meth:`repoze.bfg.configuration.Configurator.add_view`
+ method.
+
+- by adding a ``<view>`` declaration to :term:`ZCML` used by your
+ application (see :ref:`view_directive`).
+
+- by running a :term:`scan` against application source code which has
+ a :class:`repoze.bfg.view.bfg_view` decorator attached to a Python
+ object.
+
+Each of these mechanisms is completely equivalent to the other.
+
+A view might also be mapped to a URL by virtue of :term:`route
+configuration`. Route configuration is performed in one of the
+following two ways:
+
+- by using the :meth:`repoze.bfg.configuration.Configurator.add_route`
+ method
+
+- by adding a ``<route>`` declaration to :term:`ZCML` used by
+ your application.
+
+See :ref:`urldispatch_chapter` for more information on mapping URLs to
+views using routes.
+
+However a view callable is configured to be called, most view
+callables accept a single argument named ``request``. This argument
represents a :term:`WebOb` :term:`Request` object representing the
current HTTP request.
A view callable may always return a :term:`WebOb` :term:`Response`
object directly. It may optionally return another arbitrary
non-Response value. If a view callable returns a non-Response result,
-the result will be converted into a response by the :term:`renderer`
+the result must be converted into a response by the :term:`renderer`
associated with the :term:`view configuration` for the view.
-A view is mapped to one or more URLs by virtue of :term:`view
-configuration`. View configuration is performed by using the
-:meth:`repoze.bfg.configuration.Configurator.add_view` method, by
-adding a ``<view>`` statement to :term:`ZCML` used by your
-application, or by running a :term:`scan` against application source
-code which has a :class:`repoze.bfg.view.bfg_view` decorator attached
-to a Python object. Each of these mechanisms are equivalent to the
-other.
+.. note::
-A view might also be mapped to a URL by virtue of :term:`route
-configuration`. Route configuration is performed by using the
-:meth:`repoze.bfg.configuration.Configurator.add_route` method or by
-adding a ``<route>`` statement to :term:`ZCML` used by your
-application. See :ref:`urldispatch_chapter` for more information on
-mapping URLs to views using routes.
+ A :term:`view callable` is referred to in conversational shorthand
+ as a :term:`view`; in this documentation we need to be more
+ precise, however, due to the difference between view
+ *configuration* and the code that implements a view *callable*.
+
+.. note::
+
+ See :ref:`traversal_intro` for an example of how a view might be
+ found as the result of a request.
.. index::
pair: view; calling convention
@@ -45,13 +68,13 @@ mapping URLs to views using routes.
.. _function_as_view:
-Defining a View as a Function
------------------------------
+Defining a View Callable as a Function
+--------------------------------------
-The easiest way to define a view is to create a function that accepts
-a single argument named ``request`` and which returns a
+The easiest way to define a view callable is to create a function that
+accepts a single argument named ``request`` and which returns a
:term:`Response` object. For example, this is a "hello world" view
-implemented as a function:
+callable implemented as a function:
.. code-block:: python
:linenos:
@@ -68,16 +91,16 @@ implemented as a function:
.. _class_as_view:
-Defining a View as a Class
---------------------------
+Defining a View Callable as a Class
+-----------------------------------
.. note:: This feature is new as of :mod:`repoze.bfg` 0.8.1.
A view callable may also be a class instead of a function. When a
view callable is a class, the calling semantics are slightly different
than when it is a function or another non-class callable. When a view
-is a class, the class' ``__init__`` is called with the request
-parameter. As a result, an instance of the class is created.
+callable is a class, the class' ``__init__`` is called with the
+request parameter. As a result, an instance of the class is created.
Subsequently, that instance's ``__call__`` method is invoked with no
parameters. Views defined as classes must have the following traits:
@@ -115,27 +138,28 @@ represent the method expected to return a response, you can use an
.. _request_and_context_view_definitions:
-Request-And-Context View Definitions
-------------------------------------
+Request-And-Context View Callable Definitions
+---------------------------------------------
+
+Usually, view callables are defined to accept only a single argument:
+``request``. However, view callables may alternately be defined as
+classes or functions (or any callable) that accept *two* positional
+arguments: a :term:`context` as the first argument and a
+:term:`request` as the second argument.
-View callables may alternately be defined as classes or functions (or
-any callable) that accept two positional arguments: a :term:`context`
-as the first argument and a :term:`request` as the second argument.
The :term:`context` and :term:`request` arguments passed to a view
function defined in this style can be defined as follows:
context
-
An instance of a :term:`context` found via graph :term:`traversal`
or :term:`URL dispatch`. If the context is found via traversal, it
will be a :term:`model` object.
request
-
A :term:`WebOb` Request object representing the current WSGI
request.
-The following types work as views in this style:
+The following types work as view callables in this style:
#. Functions that accept two arguments: ``context``, and ``request``,
e.g.:
@@ -157,7 +181,7 @@ The following types work as views in this style:
from webob import Response
class view(object):
- __init__(self, context, request):
+ def __init__(self, context, request):
return Response('OK')
#. Arbitrary callables that have a ``__call__`` method that accepts
@@ -175,18 +199,18 @@ The following types work as views in this style:
This style of calling convention is useful for :term:`traversal` based
applications, where the context object is frequently used within the
-view code itself.
+view callable code itself.
-No matter which view calling convention is used, the view always has
-access to the context via ``request.context``.
+No matter which view calling convention is used, the view code always
+has access to the context via ``request.context``.
.. index::
pair: view; response
.. _the_response:
-View Responses
---------------
+View Callable Responses
+-----------------------
A view callable may always return an object that implements the
:term:`WebOb` :term:`Response` interface. The easiest way to return
@@ -195,18 +219,15 @@ something that implements this interface is to return a
has the following attributes will work:
status
-
The HTTP status code (including the name) for the response.
E.g. ``200 OK`` or ``401 Unauthorized``.
headerlist
-
A sequence of tuples representing the list of headers that should be
set in the response. E.g. ``[('Content-Type', 'text/html'),
('Content-Length', '412')]``
app_iter
-
An iterable representing the body of the response. This can be a
list, e.g. ``['<html><head></head><body>Hello
world!</body></html>']`` or it can be a file-like object, or any
@@ -225,26 +246,27 @@ configuration. See :ref:`views_which_use_a_renderer`.
.. _views_which_use_a_renderer:
-Writing Views Which Use a Renderer
-----------------------------------
+Writing View Callables Which Use a Renderer
+-------------------------------------------
.. note:: This feature is new as of :mod:`repoze.bfg` 1.1
-Views needn't always return a WebOb Response object. Instead, they
-may return an arbitrary Python object, with the expectation that a
-:term:`renderer` will convert that object into a response instance on
-behalf of the developer. Some renderers use a templating system;
-other renderers use object serialization techniques.
+View callables needn't always return a WebOb Response object.
+Instead, they may return an arbitrary Python object, with the
+expectation that a :term:`renderer` will convert that object into a
+response instance on behalf of the developer. Some renderers use a
+templating system; other renderers use object serialization
+techniques.
If you do not define a ``renderer`` attribute in :term:`view
configuration` for an associated :term:`view callable`, no renderer is
associated with the view. In such a configuration, an error is raised
-when a view does not return an object which implements
+when a view callable does not return an object which implements
:term:`Response` interface.
-View configuration can vary the renderer associated with a view via
-the ``renderer`` attribute. For example, this ZCML associates the
-``json`` renderer with a view:
+View configuration can vary the renderer associated with a view
+callable via the ``renderer`` attribute. For example, this ZCML
+associates the ``json`` renderer with a view:
.. code-block:: xml
:linenos:
@@ -276,6 +298,10 @@ class as a response, no renderer will be employed.
def view(request):
return HTTPFound(location='http://example.com') # renderer avoided
+Views which use a renderer can vary non-body response attributes (such
+as headers and the HTTP status code) by attaching properties to the
+request. See :ref:`response_request_attrs`.
+
Additional renderers can be added to the system as necessary via a
ZCML directive (see :ref:`adding_and_overriding_renderers`).
@@ -285,8 +311,8 @@ ZCML directive (see :ref:`adding_and_overriding_renderers`).
.. _view_configuration:
-View Configuration: Mapping Views to URLs
------------------------------------------
+View Configuration: Mapping View Callables to URLs
+--------------------------------------------------
:term:`View configuration` may be performed in one of three ways: by
using the :meth:`repoze.bfg.configuration.Configurator.add_view`
@@ -315,14 +341,14 @@ declaration in ZCML is as follows:
name="hello.html"
/>
-The above maps the ``.views.hello_world`` view function to
+The above maps the ``.views.hello_world`` view callable function to
:term:`context` objects which are instances (or subclasses) of the
Python class represented by ``.models.Hello`` when the *view name* is
``hello.html``.
.. note:: Values prefixed with a period (``.``) for the ``context``
- and ``view`` attributes of a ``view`` (such as those above) mean
- "relative to the Python package directory in which this
+ and ``view`` attributes of a ``view`` declaration (such as those
+ above) mean "relative to the Python package directory in which this
:term:`ZCML` file is stored". So if the above ``view`` declaration
was made inside a ``configure.zcml`` file that lived in the
``hello`` package, you could replace the relative ``.models.Hello``
@@ -333,7 +359,7 @@ Python class represented by ``.models.Hello`` when the *view name* is
form, in case your package's name changes. It's also shorter to
type.
-You can also declare a *default view* for a model type:
+You can also declare a *default view callable* for a model type:
.. code-block:: xml
:linenos:
@@ -343,12 +369,13 @@ You can also declare a *default view* for a model type:
view=".views.hello_world"
/>
-A *default view* has no ``name`` attribute. When a :term:`context` is
-found and there is no *view name* associated with the result of
-:term:`traversal`, the *default view* is the view that is used.
+A *default view callable* simply has no ``name`` attribute. When a
+:term:`context` is found and there is no *view name* associated with
+the result of :term:`traversal`, the *default view callable* is the
+view callable that is used.
-You can also declare that a view is good for any model type by using
-the special ``*`` character in the ``context`` attribute:
+You can also declare that a view callable is good for any model type
+by using the special ``*`` character in the ``context`` attribute:
.. code-block:: xml
:linenos:
@@ -360,8 +387,8 @@ the special ``*`` character in the ``context`` attribute:
/>
This indicates that when :mod:`repoze.bfg` identifies that the *view
-name* is ``hello.html`` against *any* :term:`context`, this view will
-be called.
+name* is ``hello.html`` against *any* :term:`context`, the
+``.views.hello_world`` view callable will be called.
A ZCML ``view`` declaration's ``view`` attribute can also name a
class. In this case, the rules described in :ref:`class_as_view`
@@ -377,19 +404,22 @@ See :ref:`view_directive` for complete ZCML directive documentation.
View Configuration Using the ``@bfg_view`` Decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-For better locality of reference, use the
+For better locality of reference, you may use the
:class:`repoze.bfg.view.bfg_view` decorator to associate your view
-functions with URLs instead of using :term:`ZCML` for the same
-purpose. :class:`repoze.bfg.view.bfg_view` can be used to associate
+functions with URLs instead of using :term:`ZCML` or imperative
+configuration for the same purpose.
+
+:class:`repoze.bfg.view.bfg_view` can be used to associate
``context``, ``name``, ``permission`` and ``request_method``,
``containment``, ``request_param`` and ``request_type``, ``attr``,
``renderer``, ``wrapper``, ``xhr``, ``accept``, and ``header``
information -- as done via the equivalent ZCML -- with a function that
-acts as a :mod:`repoze.bfg` view. All ZCML attributes (save for the
-``view`` attribute) are available in decorator form and mean precisely
-the same thing.
+acts as a :mod:`repoze.bfg` view callable. All ZCML attributes (save
+for the ``view`` attribute) are available in decorator form and mean
+precisely the same thing.
-To make :mod:`repoze.bfg` process your
+The mere existence of a ``@bfg_view`` decorator doesn't suffice to
+perform view configuration. To make :mod:`repoze.bfg` process your
:class:`repoze.bfg.view.bfg_view` declarations, you *must* do one of
the following:
@@ -405,23 +435,26 @@ the following:
.. code-block:: python
+ # config is assumed to be an instance of the
+ # repoze.bfg.configuration.Configurator class
config.scan()
-.. note:: See :ref:`configuration_module` for additional API arguments
- to the :meth:`repoze.bfg.configuration.Configurator.scan` method.
- For example, the ``scan`` method allows you to supply a ``package``
- argument to better control exactly *which* code will be scanned.
- This is the same value implied by the ``package`` attribute of the
- ZCML ``<scan>`` directive.
+If you invoke a scan, you will not need to use ZCML or imperative
+configuration to create :mod:`repoze.bfg` view declarations. Instead,
+you will be able to do all the work in
+:class:`repoze.bfg.view.bfg_view` decorators.
-Please see :ref:`scanning_chapter` for more information about what
-happens when code is scanned for configuration declarations resulting
-from use of decorators like :class:`repoze.bfg.view.bfg_view`.
+Please see :ref:`decorations_and_code_scanning` for detailed
+information about what happens when code is scanned for configuration
+declarations resulting from use of decorators like
+:class:`repoze.bfg.view.bfg_view`.
-After you do so, you will not need to use ZCML or imperative
-configuration to configure :mod:`repoze.bfg` view declarations.
-Instead, you will be able to use the :class:`repoze.bfg.view.bfg_view`
-decorator to do this work.
+See :ref:`configuration_module` for additional API arguments to the
+:meth:`repoze.bfg.configuration.Configurator.scan` method. For
+example, the method allows you to supply a ``package`` argument to
+better control exactly *which* code will be scanned. This is the same
+value implied by the ``package`` attribute of the ZCML ``<scan>``
+directive (see :ref:`scan_directive`).
.. warning:: using this feature tends to slows down application
startup slightly, as more work is performed at application startup
@@ -432,14 +465,8 @@ decorator to do this work.
framework). See :ref:`extending_chapter` for more information
about building extensible applications.
-The ``bfg_view`` Decorator
-++++++++++++++++++++++++++
-
-:class:`repoze.bfg.view.bfg_view` is a decorator which allows Python
-code to make view registrations instead of using ZCML for the same
-purpose.
-
-An example might reside in a bfg application module ``views.py``:
+An example of the :class:`repoze.bfg.view.bfg_view` decorator might
+reside in a bfg application module ``views.py``:
.. ignore-next-block
.. code-block:: python
@@ -477,6 +504,9 @@ Or replaces the need to add this imperative configuration stanza:
config.add_view(name='my_view', request_method='POST', context=MyModel,
permission='read')
+``@bfg_view`` Arguments
++++++++++++++++++++++++
+
All arguments to :class:`repoze.bfg.view.bfg_view` are optional.
Every argument to :class:`repoze.bfg.view.bfg_view` matches the
meaning of the same-named attribute in ZCML view configuration
@@ -540,11 +570,6 @@ HTTP header exists with any value in the request. See the description
of ``header`` in :ref:`view_directive` for information about the
allowable composition and matching behavior of this value.
-View lookup ordering for views registered with the
-:class:`repoze.bfg.view.bfg_view` decorator is the same as for those
-registered via ZCML. See :ref:`view_lookup_ordering` for more
-information.
-
All arguments may be omitted. For example:
.. code-block:: python
@@ -564,8 +589,26 @@ matches any model type, using no permission, registered against
requests with any request method / request type / request param /
route name / containment.
-If your view callable is a class, the
-:class:`repoze.bfg.view.bfg_view` decorator can also be used as a
+``@bfg_view`` Placement
++++++++++++++++++++++++
+
+A :class:`repoze.bfg.view.bfg_view` decorator can be placed in various
+points in your application.
+
+If your view callable is a function, it may be used as a function
+decorator:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.view import bfg_view
+ from webob import Response
+
+ @bfg_view(name='edit')
+ def edit(request):
+ return Response('edited!')
+
+If your view callable is a class, the decorator can also be used as a
class decorator in Python 2.6 and better (Python 2.5 and below do not
support class decorators). All the arguments to the decorator are the
same when applied against a class as when they are applied against a
@@ -612,11 +655,12 @@ separate view registration. For example:
:linenos:
from repoze.bfg.view import bfg_view
+ from webob import Response
@bfg_view(name='edit')
@bfg_view(name='change')
def edit(request):
- pass
+ return Response('edited!')
This registers the same view under two different names.
@@ -677,9 +721,23 @@ could be spelled equivalently as the below:
View Configuration Using the ``add_view`` Method of a Configurator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-See the :meth:`repoze.bfg.configuration.Configurator.add_view` method
-within :ref:`configuration_module` for the arguments to configure a
-view imperatively.
+The :meth:`repoze.bfg.configuration.Configurator.add_view` method
+within :ref:`configuration_module` is used to configure a view
+imperatively. The arguments to this method are very similar to the
+arguments that you provide to the ``@bfg_view`` decorator. For
+example:
+
+.. code-block:: python
+ :linenos:
+
+ from webob import Response
+
+ def hello_world(request):
+ return Response('hello!')
+
+ # config is assumed to be an instance of the
+ # repoze.bfg.configuration.Configurator class
+ config.add_view(hello_world, name='hello.html')
.. index::
pair: view; lookup ordering
@@ -747,18 +805,18 @@ looked up on the view object to return a response.
.. _using_model_interfaces:
-Using Model Interfaces
-----------------------
+Using Model Interfaces In View Configuration
+--------------------------------------------
Instead of registering your views with a ``context`` that names a
-Python model *class*, you can optionally register a view for an
-:term:`interface`. Since an interface can be attached arbitrarily to
-any model instance (as opposed to its identity being implied by only
-its class), associating a view with an interface can provide more
-flexibility for sharing a single view between two or more different
-implementations of a model type. For example, if two model object
-instances of different Python class types share the same interface,
-you can use the same view against each of them.
+Python model *class* as a context, you can optionally register a view
+callable for an :term:`interface`. Since an interface can be attached
+arbitrarily to any model instance (as opposed to its identity being
+implied by only its class), associating a view with an interface can
+provide more flexibility for sharing a single view between two or more
+different implementations of a model type. For example, if two model
+object instances of different Python class types share the same
+interface, you can use the same view against each of them.
In order to make use of interfaces in your application during view
dispatch, you must create an interface and mark up your model classes
@@ -804,11 +862,11 @@ in such a way that the interface is attached to it.
Regardless of how you associate an interface with a model instance or
a model class, the resulting ZCML to associate that interface with a
-view is the same. Assuming the above code that defines an ``IHello``
-interface lives in the root of your application, and its module is
-named "models.py", the below interface declaration will associate the
-``.views.hello_world`` view with models that implement (aka provide)
-this interface.
+view callable is the same. Assuming the above code that defines an
+``IHello`` interface lives in the root of your application, and its
+module is named "models.py", the below interface declaration will
+associate the ``.views.hello_world`` view with models that implement
+(aka provide) this interface.
.. code-block:: xml
:linenos:
@@ -821,7 +879,8 @@ this interface.
Any time a model that is determined to be the :term:`context` provides
this interface, and a view named ``hello.html`` is looked up against
-it as per the URL, the ``.views.hello_world`` view will be invoked.
+it as per the URL, the ``.views.hello_world`` view callable will be
+invoked.
Note that views registered against a model class take precedence over
views registered for any interface the model class implements when an
@@ -829,8 +888,9 @@ ambiguity arises. If a view is registered for both the class type of
the context and an interface implemented by the context's class, the
view registered for the context's class will "win".
-See :term:`Interface` in the glossary to find more information about
-interfaces.
+For more information about defining models with interfaces for use
+within view configuration, see
+:ref:`models_which_implement_interfaces`.
.. index::
pair: renderers; built-in
@@ -880,6 +940,10 @@ representing the ``str()`` serialization of the return value:
{'content': 'Hello!'}
+Views which use the string renderer can vary non-body response
+attributes by attaching properties to the request. See
+:ref:`response_request_attrs`.
+
.. index::
pair: renderer; JSON
@@ -915,7 +979,7 @@ representing the JSON serialization of the return value:
'{"content": "Hello!"}'
The return value needn't be a dictionary, but the return value must
-contain values renderable by :func:`simplejson.dumps`.
+contain values renderable by :func:`json.dumps`.
You can configure a view to use the JSON renderer in ZCML by naming
``json`` as the ``renderer`` attribute of a view configuration, e.g.:
@@ -971,7 +1035,7 @@ returns anything but a dictionary, an error will be raised.
Before passing keywords to the template, the keywords derived from the
dictionary returned by the view are augmented. The callable object
-(whatever object was used to define the ``view``) will be
+-- whatever object was used to define the ``view`` -- will be
automatically inserted into the set of keyword arguments passed to the
template as the ``view`` keyword. If the view callable was a class,
the ``view`` keyword will be an instance of that class. Also inserted
@@ -1246,18 +1310,11 @@ user does not possess the ``add`` permission relative to the current
See the :ref:`security_chapter` chapter to find out how to turn on
an authentication policy.
-.. note::
-
- Packages such as :term:`repoze.who` are capable of intercepting a
- ``Forbidden`` response and displaying a form that asks a user to
- authenticate. Use this kind of package to ask the user for
- authentication credentials.
-
.. index::
pair: view; http redirect
-Using a View to Do A HTTP Redirect
-----------------------------------
+Using a View Callable to Do A HTTP Redirect
+-------------------------------------------
You can issue an HTTP redirect from within a view by returning a
slightly different response.
@@ -1277,6 +1334,7 @@ it includes other response types for ``Unauthorized``, etc.
.. index::
triple: view; zcml; static resource
+ single: add_static_view
.. _static_resources_section:
@@ -1345,8 +1403,13 @@ See :ref:`static_directive` for detailed information.
.. note:: The :ref:`static_directive` ZCML directive is new in
:mod:`repoze.bfg` 1.1.
+.. note:: The
+ :meth:`repoze.bfg.configuration.Configurator.add_static_view`
+ method offers an imperative equivalent to the ``static`` ZCML
+ directive.
+
.. index::
- pair: generating; static resource
+ triple: generating; static resource; urls
.. _generating_static_resource_urls:
@@ -1419,8 +1482,8 @@ URLs will continue to resolve properly after the rename.
.. index::
pair: view; static resource
-Serving Static Resources Using a View
--------------------------------------
+Serving Static Resources Using a View Callable
+----------------------------------------------
For more flexibility, static resources can be served by a view which
you register manually. For example, you may want static resources to
@@ -1538,7 +1601,7 @@ having high-order (non-ASCII) characters in data contained within form
submissions is exceedingly common, and because the UTF-8 encoding is
the most common encoding used on the web for non-ASCII character data,
and because working and storing Unicode values is much saner than
-working with an storing bytestrings, :mod:`repoze.bfg` configures the
+working with and storing bytestrings, :mod:`repoze.bfg` configures the
:term:`WebOb` request machinery to attempt to decode form submission
values into Unicode from the UTF-8 character set implicitly. This
implicit decoding happens when view code obtains form field values via
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index 4284c4110..3df1bac32 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -352,7 +352,7 @@ tuples; all the keys are ordered, and all the values are ordered.
triple: response; attributes; special
Special :mod:`repoze.bfg` Attributes Added to the Request
----------------------------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:mod:`repoze.bfg` adds special attributes to a request as the result
of :term:`traversal`. See :ref:`traversal_related_side_effects` for a
diff --git a/docs/thanks.rst b/docs/thanks.rst
deleted file mode 100644
index 6f2b07ceb..000000000
--- a/docs/thanks.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-Author Thanks
-=============
-
-This book is dedicated to my grandmother, Dorothy Phillips.
-
-Thanks to the following people for providing expertise, resources, and
-software. Without the help of these folks, neither this book nor the
-software which it details would exist: Paul Everitt, Tres Seaver,
-Malthe Borch, Carlos de la Guardia, Georg Brandl, Simon Oram of
-Electrosoup, Ian Bicking of the Open Planning Project, Jim Fulton of
-Zope Corporation, Tom Moroz of the Open Society Institute, and Todd
-Koym of Environmental Health Sciences.
-
-Special thanks to Guido van Rossum and Tim Peters for Python.
-
-Special thanks also to Tricia for putting up with me.
-
diff --git a/docs/tutorials/bfgwiki/authorization.rst b/docs/tutorials/bfgwiki/authorization.rst
index 13443ca53..b2c5330f6 100644
--- a/docs/tutorials/bfgwiki/authorization.rst
+++ b/docs/tutorials/bfgwiki/authorization.rst
@@ -11,6 +11,9 @@ allowing anyone with access to the server to view pages.
*authentication*. We'll make use of both features to provide security
to our application.
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki-1.2/authorization>`_.
+
Configuring a ``repoze.bfg`` Authentication Policy
--------------------------------------------------
diff --git a/docs/tutorials/bfgwiki/basiclayout.rst b/docs/tutorials/bfgwiki/basiclayout.rst
index f0bf8ced8..213f238fd 100644
--- a/docs/tutorials/bfgwiki/basiclayout.rst
+++ b/docs/tutorials/bfgwiki/basiclayout.rst
@@ -7,6 +7,9 @@ but they provide a good orientation for the high-level patterns common
to most :term:`traversal` -based :mod:`repoze.bfg` (and :term:`ZODB`
based) projects.
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki-1.2/basiclayout>`_.
+
``__init__.py``
---------------
diff --git a/docs/tutorials/bfgwiki/definingmodels.rst b/docs/tutorials/bfgwiki/definingmodels.rst
index effbaa4bf..475e35442 100644
--- a/docs/tutorials/bfgwiki/definingmodels.rst
+++ b/docs/tutorials/bfgwiki/definingmodels.rst
@@ -11,6 +11,9 @@ objects. A single instance of the "Wiki" class will serve as a
container for "Page" objects, which will be instances of the "Page"
class.
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki-1.2/models>`_.
+
Deleting the Database
---------------------
diff --git a/docs/tutorials/bfgwiki/definingviews.rst b/docs/tutorials/bfgwiki/definingviews.rst
index b8c1ef37b..72101d258 100644
--- a/docs/tutorials/bfgwiki/definingviews.rst
+++ b/docs/tutorials/bfgwiki/definingviews.rst
@@ -25,6 +25,9 @@ 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`.
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki-1.2/views>`_.
+
Adding View Functions
=====================
@@ -232,11 +235,10 @@ 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
+our package's ``templates/static`` directory. This file is a little
+too long to replicate within the body of this guide, however it is
+available `online
+<http://docs.repoze.org/bfgwiki-1.2/views/tutorial/templates/static/style.css>`_.
This CSS file will be accessed via
e.g. ``http://localhost:6543/static/style.css`` by virtue of the
diff --git a/docs/tutorials/bfgwiki/index.rst b/docs/tutorials/bfgwiki/index.rst
index 4e6d9b306..3ba79b714 100644
--- a/docs/tutorials/bfgwiki/index.rst
+++ b/docs/tutorials/bfgwiki/index.rst
@@ -8,7 +8,9 @@ application to a developer familiar with Python. When we're done with
the tutorial, the developer will have created a basic Wiki application
with authentication.
-Contents:
+For cut and paste purposes, the source code for all stages of this
+tutorial can be browsed at `docs.repoze.org
+<http://docs.repoze.org/bfgwiki-1.2>`_.
.. toctree::
:maxdepth: 2
diff --git a/docs/tutorials/bfgwiki2/authorization.rst b/docs/tutorials/bfgwiki2/authorization.rst
index ea4c7b4e4..7383fe327 100644
--- a/docs/tutorials/bfgwiki2/authorization.rst
+++ b/docs/tutorials/bfgwiki2/authorization.rst
@@ -13,6 +13,10 @@ continue allowing anyone with access to the server to view pages.
*authentication*. We'll make use of both features to provide security
to our application.
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org
+<http://docs.repoze.org/bfgwiki2-1.2/authorization>`_.
+
Adding A Root Factory
---------------------
diff --git a/docs/tutorials/bfgwiki2/basiclayout.rst b/docs/tutorials/bfgwiki2/basiclayout.rst
index d882832d5..5392660da 100644
--- a/docs/tutorials/bfgwiki2/basiclayout.rst
+++ b/docs/tutorials/bfgwiki2/basiclayout.rst
@@ -6,6 +6,9 @@ The starter files generated by the ``bfg_routesalchemy`` template are
basic, but they provide a good orientation for the high-level patterns
common to most :term:`url dispatch` -based :mod:`repoze.bfg` projects.
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki2-1.2/basiclayout>`_.
+
``__init__.py``
---------------
diff --git a/docs/tutorials/bfgwiki2/definingmodels.rst b/docs/tutorials/bfgwiki2/definingmodels.rst
index 0cae00ede..214a5e908 100644
--- a/docs/tutorials/bfgwiki2/definingmodels.rst
+++ b/docs/tutorials/bfgwiki2/definingmodels.rst
@@ -6,6 +6,9 @@ The first change we'll make to our stock paster-generated application
will be to define a :term:`model` constructor representing a wiki
page. We'll do this inside our ``models.py`` file.
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki2-1.2/models>`_.
+
Making Edits to ``models.py``
-----------------------------
diff --git a/docs/tutorials/bfgwiki2/definingviews.rst b/docs/tutorials/bfgwiki2/definingviews.rst
index db7dcfbe0..1d40d0051 100644
--- a/docs/tutorials/bfgwiki2/definingviews.rst
+++ b/docs/tutorials/bfgwiki2/definingviews.rst
@@ -29,6 +29,9 @@ invoked, matching this path, the matchdict dictionary attached to the
request passed to the view would have a ``one`` key with the value
``foo`` and a ``two`` key with the value ``bar``.
+The source code for this tutorial stage can be browsed at
+`docs.repoze.org <http://docs.repoze.org/bfgwiki2-1.2/views>`_.
+
Declaring Dependencies in Our ``setup.py`` File
===============================================
@@ -246,11 +249,10 @@ 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
+our package's ``templates/static`` directory. This file is a little
+too long to replicate within the body of this guide, however it is
+available `online
+<http://docs.repoze.org/bfgwiki2-1.2/views/tutorial/templates/static/style.css>`_.
This CSS file will be accessed via
e.g. ``http://localhost:6543/static/style.css`` by virtue of the
diff --git a/docs/tutorials/bfgwiki2/index.rst b/docs/tutorials/bfgwiki2/index.rst
index f97972766..42189f3e6 100644
--- a/docs/tutorials/bfgwiki2/index.rst
+++ b/docs/tutorials/bfgwiki2/index.rst
@@ -8,7 +8,9 @@ This tutorial introduces a :term:`SQLAlchemy` and :term:`url dispatch`
Python. When the tutorial is finished, the developer will have
created a basic Wiki application with authentication.
-Contents:
+For cut and paste purposes, the source code for all stages of this
+tutorial can be browsed at `docs.repoze.org
+<http://docs.repoze.org/bfgwiki2-1.2>`_.
.. toctree::
:maxdepth: 2
diff --git a/docs/whatsnew-1.2.rst b/docs/whatsnew-1.2.rst
index cae307c14..e3f89fbe7 100644
--- a/docs/whatsnew-1.2.rst
+++ b/docs/whatsnew-1.2.rst
@@ -279,10 +279,6 @@ Documentation Enhancements
configuration; doing :mod:`repoze.bfg` application configuration
imperatively was previously much more difficult.
-- A narrative documentation chapter entitled "Configuration,
- Decorations and Code Scanning" (:ref:`scanning_chapter`) explaining
- ZCML- vs. imperative- vs. decorator-based configuration equivalence.
-
- The "ZCML Hooks" chapter has been renamed to "Hooks"
(:ref:`hooks_chapter`); it documents how to override hooks now via
imperative configuration and ZCML.
diff --git a/repoze/bfg/events.py b/repoze/bfg/events.py
index 1c9ca174c..6c64fa2ec 100644
--- a/repoze/bfg/events.py
+++ b/repoze/bfg/events.py
@@ -20,7 +20,17 @@ class NewResponse(object):
whenever any :mod:`repoze.bfg` view returns a :term:`response`.
The instance has an attribute, ``response``, which is the response
object returned by the view. This class implements the
- :class:`repoze.bfg.interfaces.INewResponse` interface."""
+ :class:`repoze.bfg.interfaces.INewResponse` interface.
+
+ .. note::
+
+ Postprocessing a response is usually better handled in a WSGI
+ :term:`middleware` component than in subscriber code that is
+ called by a :class:`repoze.bfg.interfaces.INewResponse` event.
+ The :class:`repoze.bfg.interfaces.INewResponse` event exists
+ almost purely for symmetry with the
+ :class:`repoze.bfg.interfaces.INewRequest` event.
+ """
implements(INewResponse)
def __init__(self, response):
self.response = response