summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt19
-rw-r--r--CONTRIBUTORS.txt4
-rw-r--r--docs/api/authentication.rst2
-rw-r--r--docs/designdefense.rst61
-rw-r--r--docs/glossary.rst18
-rw-r--r--docs/narr/advconfig.rst10
-rw-r--r--docs/narr/environment.rst16
-rw-r--r--docs/narr/extending.rst6
-rw-r--r--docs/narr/hooks.rst35
-rw-r--r--docs/narr/hybrid.rst46
-rw-r--r--docs/narr/i18n.rst14
-rw-r--r--docs/narr/project.rst14
-rw-r--r--docs/narr/renderers.rst28
-rw-r--r--docs/narr/resources.rst16
-rw-r--r--docs/narr/traversal.rst30
-rw-r--r--docs/narr/viewconfig.rst24
-rw-r--r--docs/narr/zca.rst8
-rw-r--r--docs/tutorials/modwsgi/index.rst2
-rw-r--r--docs/tutorials/wiki/authorization.rst30
-rw-r--r--docs/tutorials/wiki/definingviews.rst6
-rw-r--r--docs/tutorials/wiki/index.rst4
-rw-r--r--docs/tutorials/wiki/installation.rst4
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py4
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py4
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py4
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/tests.py10
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py4
-rw-r--r--docs/tutorials/wiki/tests.rst2
-rw-r--r--docs/tutorials/wiki2/authorization.rst87
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst6
-rw-r--r--docs/tutorials/wiki2/definingmodels.rst4
-rw-r--r--docs/tutorials/wiki2/definingviews.rst98
-rw-r--r--docs/tutorials/wiki2/index.rst5
-rw-r--r--docs/tutorials/wiki2/installation.rst4
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models.py7
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views.py8
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models.py7
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests.py266
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models.py5
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views.py13
-rw-r--r--docs/tutorials/wiki2/tests.rst74
-rw-r--r--docs/whatsnew-1.1.rst13
-rw-r--r--pyramid/authentication.py44
-rw-r--r--pyramid/i18n.py7
-rw-r--r--pyramid/mako_templating.py2
-rw-r--r--pyramid/scaffolds/zodb/+package+/__init__.py_tmpl4
-rw-r--r--pyramid/settings.py2
-rw-r--r--pyramid/tests/fixtures/nonminimal.mak1
-rw-r--r--pyramid/tests/test_authentication.py98
-rw-r--r--pyramid/tests/test_i18n.py13
-rw-r--r--pyramid/tests/test_mako_templating.py5
-rw-r--r--pyramid/tests/test_urldispatch.py4
-rw-r--r--pyramid/urldispatch.py6
53 files changed, 900 insertions, 308 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index a2976d1a2..c58ff755b 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -37,6 +37,8 @@ Documentation
- Added "What's New in Pyramid 1.1" to HTML rendering of documentation.
+- Added API docs for ``pyramid.authentication.SessionAuthenticationPolicy``.
+
- Added API docs for ``pyramid.httpexceptions.responsecode``.
- Added "HTTP Exceptions" section to Views narrative chapter including a
@@ -45,6 +47,16 @@ Documentation
Features
--------
+- Add support for language fallbacks: when trying to translate for a
+ specific territory (such as ``en_GB``) fall back to translations
+ for the language (ie ``en``). This brings the translation behaviour in line
+ with GNU gettext and fixes partially translated texts when using C
+ extensions.
+
+- New authentication policy:
+ ``pyramid.authentication.SessionAuthenticationPolicy``, which uses a session
+ to store credentials.
+
- Accessing the ``response`` attribute of a ``pyramid.request.Request``
object (e.g. ``request.response`` within a view) now produces a new
``pyramid.response.Response`` object. This feature is meant to be used
@@ -285,6 +297,13 @@ Deprecations
Behavior Changes
----------------
+- The default Mako renderer is now configured to escape all HTML in
+ expression tags. This is intended to help prevent XSS attacks caused by
+ rendering unsanitized input from users. To revert this behavior in user's
+ templates, they need to filter the expression through the 'n' filter.
+ For example, ${ myhtml | n }.
+ See https://github.com/Pylons/pyramid/issues/193.
+
- A custom request factory is now required to return a request object that
has a ``response`` attribute (or "reified"/lazy property) if they the
request is meant to be used in a view that uses a renderer. This
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index f2195de70..cdc011f87 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -138,3 +138,7 @@ Contributors
- Juliusz Gonera, 2011/04/17
- Philip Jenvey, 2011/04/24
+
+- Michael Merickel, 2011/5/25
+
+- Christoph Zwerschke, 2011/06/07
diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst
index bf7f8f8d5..5d4dbd9e3 100644
--- a/docs/api/authentication.rst
+++ b/docs/api/authentication.rst
@@ -14,6 +14,8 @@ Authentication Policies
.. autoclass:: RemoteUserAuthenticationPolicy
+ .. autoclass:: SessionAuthenticationPolicy
+
Helper Classes
~~~~~~~~~~~~~~
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index de6c0af33..77711016d 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -1011,6 +1011,67 @@ which returns Zope3-security-proxy-wrapped objects for each traversed object
(including the :term:`context` and the :term:`root`). This would have the
effect of creating a more Zope3-like environment without much effort.
+.. _simpler_traversal_model:
+
+Pyramid has Simpler Traversal Machinery than Does Zope
+------------------------------------------------------
+
+Zope's default traverser:
+
+- Allows developers to mutate the traversal name stack while traversing (they
+ can add and remove path elements).
+
+- Attempts to use an adaptation to obtain the "next" element in the path from
+ the currently traversed object, falling back to ``__bobo_traverse__``,
+ ``__getitem__`` and eventually ``__getattr__``.
+
+Zope's default traverser allows developers to mutate the traversal name stack
+during traversal by mutating ``REQUEST['TraversalNameStack']``. Pyramid's
+default traverser (``pyramid.traversal.ResourceTreeTraverser``) does not
+offer a way to do this; it does not maintain a stack as a request attribute
+and, even if it did, it does not pass the request to resource objects while
+it's traversing. While it was handy at times, this feature was abused in
+frameworks built atop Zope (like CMF and Plone), often making it difficult to
+tell exactly what was happening when a traversal didn't match a view. I felt
+it was better to make folks that wanted the feature replace the traverser
+rather than build that particular honey pot in to the default traverser.
+
+Zope uses multiple mechanisms to attempt to obtain the next element in the
+resource tree based on a name. It first tries an adaptation of the current
+resource to ``ITraversable``, and if that fails, it falls back to attempting
+number of magic methods on the resource (``__bobo_traverse__``,
+``__getitem__``, and ``__getattr__``). My experience while both using Zope
+and attempting to reimplement its publisher in ``repoze.zope2`` led me to
+believe the following:
+
+- The *default* traverser should be as simple as possible. Zope's publisher
+ is somewhat difficult to follow and replicate due to the fallbacks it tried
+ when one traversal method failed. It is also slow.
+
+- The *entire traverser* should be replaceable, not just elements of the
+ traversal machinery. Pyramid has a few "big" components rather than a
+ plethora of small ones. If the entire traverser is replaceable, it's an
+ antipattern to make portions of the default traverser replaceable. Doing
+ so is a "knobs on knobs" pattern, which is unfortunately somewhat endemic
+ in Zope. In a "knobs on knobs" pattern, a replaceable subcomponent of a
+ larger component is made configurable using the same configuration
+ mechanism that can be used to replace the larger component. For example,
+ in Zope, you can replace the default traverser by registering an adapter.
+ But you can also (or alternately) control how the default traverser
+ traverses by registering one or more adapters. As a result of being able
+ to either replace the larger component entirely or turn knobs on the
+ default implementation of the larger component, no one understands when (or
+ whether) they should ever override the larger component entrirely. This
+ results, over time, in a "rusting together" of the larger "replaceable"
+ component and the framework itself, because people come to depend on the
+ availability of the default component in order just to turn its knobs. The
+ default component effectively becomes part of the framework, which entirely
+ subverts the goal of making it replaceable. In Pyramid, typically if a
+ component is replaceable, it will itself have no knobs (it will be "solid
+ state"). If you want to influence behavior controlled by that component,
+ you will replace the component instead of turning knobs attached to the
+ component.
+
.. _microframeworks_smaller_hello_world:
Microframeworks Have Smaller Hello World Programs
diff --git a/docs/glossary.rst b/docs/glossary.rst
index d3ba9a545..e45317dae 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -55,7 +55,7 @@ Glossary
For example, the asset specification
``my.package:static/baz.css`` identifies the file named
``baz.css`` in the ``static`` subdirectory of the ``my.package``
- Python :term:`package`. See :ref:`asset_specifications` for more
+ Python :term:`package`. See :ref:`asset_specifications` for more
info.
package
@@ -65,7 +65,7 @@ Glossary
module
A Python source file; a file on the filesystem that typically ends with
- the extension ``.py`` or ``.pyc``. Modules often live in a
+ the extension ``.py`` or ``.pyc``. Modules often live in a
:term:`package`.
project
@@ -214,9 +214,9 @@ Glossary
a :term:`context` resource. A permission is associated with a view name
and a resource type by the developer. Resources are decorated with
security declarations (e.g. an :term:`ACL`), which reference these
- tokens also. Permissions are used by the active to security policy to
+ tokens also. Permissions are used by the active security policy to
match the view permission against the resources's statements about which
- permissions are granted to which principal in a context in order to to
+ permissions are granted to which principal in a context in order to
answer the question "is this user allowed to do this". Examples of
permissions: ``read``, or ``view_blog_entries``.
@@ -356,14 +356,14 @@ Glossary
METAL
`Macro Expansion for TAL <http://wiki.zope.org/ZPT/METAL>`_, a
part of :term:`ZPT` which makes it possible to share common look
- and feel between templates.
+ and feel between templates.
Genshi
An `XML templating language <http://pypi.python.org/pypi/Genshi/>`_
by Christopher Lenz.
Jinja2
- A `text templating language <http://jinja.pocoo.org/2/>`_ by Armin
+ A `text templating language <http://jinja.pocoo.org/2/>`_ by Armin
Ronacher.
Routes
@@ -401,7 +401,7 @@ Glossary
root
The object at which :term:`traversal` begins when :app:`Pyramid`
searches for a :term:`context` resource (for :term:`URL Dispatch`, the
- root is *always* the context resource unless the ``traverse=`` argument
+ root is *always* the context resource unless the ``traverse=`` argument
is used in route configuration).
subpath
@@ -862,7 +862,7 @@ Glossary
ZCML
`Zope Configuration Markup Language
<http://www.muthukadan.net/docs/zca.html#zcml>`_, an XML dialect
- used by Zope and :term:`pyramid_zcml` for configuration tasks.
+ used by Zope and :term:`pyramid_zcml` for configuration tasks.
ZCML directive
A ZCML "tag" such as ``<view>`` or ``<route>``.
@@ -904,5 +904,3 @@ Glossary
A :term:`response` that is generated as the result of a raised exception
being caught by an :term:`exception view`.
-
-
diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst
index 5ee554284..3bd9c2a4e 100644
--- a/docs/narr/advconfig.rst
+++ b/docs/narr/advconfig.rst
@@ -86,9 +86,9 @@ that ends something like this:
for action in resolveConflicts(self.actions):
File "zope/configuration/config.py", line 1507, in resolveConflicts
raise ConfigurationConflictError(conflicts)
- zope.configuration.config.ConfigurationConflictError:
+ zope.configuration.config.ConfigurationConflictError:
Conflicting configuration actions
- For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>,
+ For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>,
None, None, None, None, None, False, None, None, None)
('app.py', 14, '<module>', 'config.add_view(hello_world)')
('app.py', 17, '<module>', 'config.add_view(hello_world)')
@@ -291,7 +291,7 @@ These are the methods of the configurator which provide conflict detection:
:meth:`~pyramid.config.Configurator.add_route`,
:meth:`~pyramid.config.Configurator.add_renderer`,
:meth:`~pyramid.config.Configurator.set_request_factory`,
-:meth:`~pyramid.config.Configurator.set_renderer_globals_factory`
+:meth:`~pyramid.config.Configurator.set_renderer_globals_factory`,
:meth:`~pyramid.config.Configurator.set_locale_negotiator` and
:meth:`~pyramid.config.Configurator.set_default_permission`.
@@ -425,7 +425,7 @@ For example:
if __name__ == '__main__':
config = Configurator()
- config.add_directive('add_newrequest_subscriber',
+ config.add_directive('add_newrequest_subscriber',
add_newrequest_subscriber)
Once :meth:`~pyramid.config.Configurator.add_directive` is called, a user can
@@ -450,7 +450,7 @@ code in a package named ``pyramid_subscriberhelpers``:
:linenos:
def includeme(config)
- config.add_directive('add_newrequest_subscriber',
+ config.add_directive('add_newrequest_subscriber',
add_newrequest_subscriber)
The user of the add-on package ``pyramid_subscriberhelpers`` would then be
diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst
index e15f7810c..3b938c09c 100644
--- a/docs/narr/environment.rst
+++ b/docs/narr/environment.rst
@@ -13,7 +13,7 @@
single: environment variables
single: ini file settings
single: PasteDeploy settings
-
+
.. _environment_chapter:
Environment Variables and ``.ini`` File Settings
@@ -84,7 +84,7 @@ when this value is true. See also :ref:`debug_authorization_section`.
| ``PYRAMID_DEBUG_AUTHORIZATION`` | ``debug_authorization`` |
| | |
| | |
-| | |
+| | |
+---------------------------------+-----------------------------+
Debugging Not Found Errors
@@ -259,7 +259,7 @@ List of string filter names that will be applied to all Mako expressions.
Mako Import
+++++++++++
-String list of Python statements, typically individual “import” lines, which
+String list of Python statements, typically individual "import" lines, which
will be placed into the module level preamble of all generated Python modules.
@@ -330,7 +330,7 @@ settings that do not start with ``debug_*`` such as
``reload_templates``.
If you want to turn all ``reload`` settings (every setting that starts
-with ``reload_``). on in one fell swoop, you can use
+with ``reload_``) on in one fell swoop, you can use
``PYRAMID_RELOAD_ALL=1`` as an environment variable setting or you may use
``reload_all=true`` in the config file. Note that this does not
affect settings that do not start with ``reload_*`` such as
@@ -341,10 +341,10 @@ affect settings that do not start with ``reload_*`` such as
most useful during development, where you may wish to augment or
override the more permanent settings in the configuration file.
This is useful because many of the reload and debug settings may
- have performance or security (i.e., disclosure) implications
+ have performance or security (i.e., disclosure) implications
that make them undesirable in a production environment.
-.. index::
+.. index::
single: reload_templates
single: reload_assets
@@ -442,7 +442,7 @@ Here's how:
registry = pyramid.threadlocal.get_current_registry()
settings = registry.settings
debug_frobnosticator = settings['debug_frobnosticator']
-
-
+
+
diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst
index 9c96fd605..f62c7e6bb 100644
--- a/docs/narr/extending.rst
+++ b/docs/narr/extending.rst
@@ -120,7 +120,7 @@ are declarations made using the :meth:`pyramid.config.Configurator.add_view`
method. Assets are files that are
accessed by :app:`Pyramid` using the :term:`pkg_resources` API such as static
files and templates via a :term:`asset specification`. Other directives and
-configurator methods also deal in routes, views, and assets. For example,
+configurator methods also deal in routes, views, and assets. For example, the
``add_handler`` directive of the ``pyramid_handlers`` package adds a single
route, and some number of views.
@@ -163,7 +163,7 @@ views or routes which performs overrides.
if __name__ == '__main__':
config.scan('someotherpackage')
config.commit()
- config.add_view('mypackage.views.myview', name='myview'
+ config.add_view('mypackage.views.myview', name='myview')
Once this is done, you should be able to extend or override the application
like any other (see :ref:`extending_the_application`).
@@ -201,7 +201,7 @@ like this:
application (e.g. ``python setup.py develop`` or ``python setup.py
install``).
-- Change the ``main`` function in the new package's ``__init__py`` to include
+- Change the ``main`` function in the new package's ``__init__.py`` to include
the original :app:`Pyramid` application's configuration functions via
:meth:`pyramid.config.Configurator.include` statements or a :term:`scan`.
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 8e5b93ed4..8426f11fd 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -85,7 +85,7 @@ The default forbidden response has a 403 status code and is very plain, but
the view which generates it can be overridden as necessary.
The :term:`forbidden view` callable is a view callable like any other. The
-:term:`view configuration` which causes it to be a "not found" view consists
+:term:`view configuration` which causes it to be a "forbidden" view consists
only of naming the :exc:`pyramid.httpexceptions.HTTPForbidden` class as the
``context`` of the view configuration.
@@ -183,7 +183,7 @@ Adding Renderer Globals
-----------------------
Whenever :app:`Pyramid` handles a request to perform a rendering (after a
-view with a ``renderer=`` configuration attribute is invoked, or when the any
+view with a ``renderer=`` configuration attribute is invoked, or when any
of the methods beginning with ``render`` within the :mod:`pyramid.renderers`
module are called), *renderer globals* can be injected into the *system*
values sent to the renderer. By default, no renderer globals are injected,
@@ -200,7 +200,7 @@ callable object or a :term:`dotted Python name` representing such a callable.
:linenos:
def renderer_globals_factory(system):
- return {'a':1}
+ return {'a': 1}
config = Configurator(
renderer_globals_factory=renderer_globals_factory)
@@ -221,7 +221,7 @@ already constructed a :term:`configurator` it can also be registered via the
from pyramid.config import Configurator
def renderer_globals_factory(system):
- return {'a':1}
+ return {'a': 1}
config = Configurator()
config.set_renderer_globals_factory(renderer_globals_factory)
@@ -238,8 +238,8 @@ Using The Before Render Event
-----------------------------
Subscribers to the :class:`pyramid.events.BeforeRender` event may introspect
-the and modify the set of :term:`renderer globals` before they are passed to
-a :term:`renderer`. This event object iself has a dictionary-like interface
+and modify the set of :term:`renderer globals` before they are passed to a
+:term:`renderer`. This event object iself has a dictionary-like interface
that can be used for this purpose. For example:
.. code-block:: python
@@ -485,7 +485,7 @@ resource by adding a registerAdapter call for
from myapp.traversal import URLGenerator
from myapp.resources import MyRoot
- config.registry.registerAdapter(URLGenerator, (MyRoot, Interface),
+ config.registry.registerAdapter(URLGenerator, (MyRoot, Interface),
IContextURL)
In the above example, the ``myapp.traversal.URLGenerator`` class will be used
@@ -626,7 +626,7 @@ Using a View Mapper
The default calling conventions for view callables are documented in the
:ref:`views_chapter` chapter. You can change the way users define view
-callbles by employing a :term:`view mapper`.
+callables by employing a :term:`view mapper`.
A view mapper is an object that accepts a set of keyword arguments and which
returns a callable. The returned callable is called with the :term:`view
@@ -740,24 +740,22 @@ follows:
:linenos:
import venusian
- from pyramid.threadlocal import get_current_registry
from mypackage.interfaces import IMyUtility
-
+
class registerFunction(object):
-
+
def __init__(self, path):
self.path = path
def register(self, scanner, name, wrapped):
registry = scanner.config.registry
registry.getUtility(IMyUtility).register(
- self.path, wrapped
- )
-
+ self.path, wrapped)
+
def __call__(self, wrapped):
venusian.attach(wrapped, self.register)
return wrapped
-
+
This decorator could then be used to register functions throughout
your code:
@@ -776,16 +774,17 @@ performed, enabling you to set up the utility in advance:
from paste.httpserver import serve
from pyramid.config import Configurator
+ from mypackage.interfaces import IMyUtility
class UtilityImplementation:
- implements(ISomething)
+ implements(IMyUtility)
def __init__(self):
self.registrations = {}
- def register(self,path,callable_):
- self.registrations[path]=callable_
+ def register(self, path, callable_):
+ self.registrations[path] = callable_
if __name__ == '__main__':
config = Configurator()
diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst
index f8ed743fb..97adaeafd 100644
--- a/docs/narr/hybrid.rst
+++ b/docs/narr/hybrid.rst
@@ -175,7 +175,7 @@ match is straightforward. When a route is matched:
Root factories related to a route were explained previously within
:ref:`route_factories`. Both the global root factory and default
root factory were explained previously within
- :ref:`the_resource_tree`.
+ :ref:`the_resource_tree`.
.. _using_traverse_in_a_route_pattern:
@@ -216,7 +216,7 @@ root factory. Once :term:`traversal` has found a :term:`context` resource,
have been invoked in a "pure" traversal-based application.
Let's assume there is no *global* :term:`root factory` configured in
-this application. The *default* :term:`root factory` cannot be traversed:
+this application. The *default* :term:`root factory` cannot be traversed:
it has no useful ``__getitem__`` method. So we'll need to associate
this route configuration with a custom root factory in order to
create a useful hybrid application. To that end, let's imagine that
@@ -233,8 +233,8 @@ we've created a root factory that looks like so in a module named
def __getitem__(self, name):
return self.subobjects[name]
- root = Traversable(
- {'a':Resource({'b':Resource({'c':Resource({})})})}
+ root = Resource(
+ {'a': Resource({'b': Resource({'c': Resource({})})})}
)
def root_factory(request):
@@ -247,20 +247,20 @@ configuration statement:
.. code-block:: python
:linenos:
- config.add_route('home', '{foo}/{bar}/*traverse',
+ config.add_route('home', '{foo}/{bar}/*traverse',
factory='mypackage.routes.root_factory')
The ``factory`` above points at the function we've defined. It will return
-an instance of the ``Traversable`` class as a root object whenever this route
-is matched. Instances of the``Resource`` class can be used for tree
-traversal because they have a ``__getitem__`` method that does something
-nominally useful. Since traversal uses ``__getitem__`` to walk the resources
-of a resource tree, using traversal against the root resource implied by our
-route statement is a reasonable thing to do.
+an instance of the ``Resource`` class as a root object whenever this route is
+matched. Instances of the ``Resource`` class can be used for tree traversal
+because they have a ``__getitem__`` method that does something nominally
+useful. Since traversal uses ``__getitem__`` to walk the resources of a
+resource tree, using traversal against the root resource implied by our route
+statement is a reasonable thing to do.
.. note::
- We could have also used our ``root_factory`` callable as the
+ We could have also used our ``root_factory`` function as the
``root_factory`` argument of the
:class:`~pyramid.config.Configurator` constructor, instead
of associating it with a particular route inside the route's
@@ -279,12 +279,12 @@ instance named ``root`` in ``routes.py``.
If the URL that matched a route with the pattern ``{foo}/{bar}/*traverse``,
is ``http://example.com/one/two/a/b/c``, the traversal path used
against the root object will be ``a/b/c``. As a result,
-:app:`Pyramid` will attempt to traverse through the edges ``a``,
-``b``, and ``c``, beginning at the root object.
+:app:`Pyramid` will attempt to traverse through the edges ``'a'``,
+``'b'``, and ``'c'``, beginning at the root object.
In our above example, this particular set of traversal steps will mean that
-the :term:`context` resource of the view would be the ``Traversable`` object
-we've named ``c`` in our bogus resource tree and the :term:`view name`
+the :term:`context` resource of the view would be the ``Resource`` object
+we've named ``'c'`` in our bogus resource tree and the :term:`view name`
resulting from traversal will be the empty string; if you need a refresher
about why this outcome is presumed, see :ref:`traversal_algorithm`.
@@ -297,7 +297,7 @@ invoked after a route matches:
.. code-block:: python
:linenos:
- config.add_route('home', '{foo}/{bar}/*traverse',
+ config.add_route('home', '{foo}/{bar}/*traverse',
factory='mypackage.routes.root_factory')
config.add_view('mypackage.views.myview', route_name='home')
@@ -327,11 +327,11 @@ when a hybrid route is matched:
.. code-block:: python
:linenos:
- config.add_route('home', '{foo}/{bar}/*traverse',
+ config.add_route('home', '{foo}/{bar}/*traverse',
factory='mypackage.routes.root_factory')
- config.add_view('mypackage.views.myview', name='home')
- config.add_view('mypackage.views.another_view', name='another',
- route_name='home')
+ config.add_view('mypackage.views.myview', route_name='home')
+ config.add_view('mypackage.views.another_view', route_name='home',
+ name='another')
The ``add_view`` call for ``mypackage.views.another_view`` above names a
different view and, more importantly, a different :term:`view name`. The
@@ -373,12 +373,12 @@ Here's a use of the ``traverse`` pattern in a call to
:linenos:
config.add_route('abc', '/articles/{article}/edit',
- traverse='/articles/{article}')
+ traverse='/{article}')
The syntax of the ``traverse`` argument is the same as it is for
``pattern``.
-If, as above, the ``pattern`` provided is ``articles/{article}/edit``,
+If, as above, the ``pattern`` provided is ``/articles/{article}/edit``,
and the ``traverse`` argument provided is ``/{article}``, when a
request comes in that causes the route to match in such a way that the
``article`` match value is ``1`` (when the request URI is
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index e928c6efb..c21a19b5b 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -95,7 +95,7 @@ translations of the same msgid, in case they conflict.
:linenos:
from pyramid.i18n import TranslationString
- ts = TranslationString('Add ${number}', mapping={'number':1},
+ ts = TranslationString('Add ${number}', mapping={'number':1},
domain='form')
The above translation string named a domain of ``form``. A
@@ -170,7 +170,7 @@ to:
:linenos:
from pyramid.i18n import TranslationString as _
- ts = _('Add ${number}', msgid='add-number', mapping={'number':1},
+ ts = _('Add ${number}', msgid='add-number', mapping={'number':1},
domain='pyramid')
You can set up your own translation string factory much like the one
@@ -231,7 +231,7 @@ GNU gettext uses three types of files in the translation framework,
A ``.pot`` file is created by a program which searches through your
project's source code and which picks out every :term:`message
- identifier` passed to one of the '``_()`` functions
+ identifier` passed to one of the ``_()`` functions
(eg. :term:`translation string` constructions). The list of all
message identifiers is placed into a ``.pot`` file, which serves as
a template for creating ``.po`` files.
@@ -288,7 +288,7 @@ like so:
.. code-block:: text
C> cd \my\virtualenv
- C> bin\easy_install Babel lingua
+ C> Scripts\easy_install Babel lingua
.. index::
single: Babel; message extractors
@@ -535,7 +535,7 @@ translation in a view component of an application might look like so:
from pyramid.i18n import get_localizer
from pyramid.i18n import TranslationString
- ts = TranslationString('Add ${number}', mapping={'number':1},
+ ts = TranslationString('Add ${number}', mapping={'number':1},
domain='pyramid')
def aview(request):
@@ -844,7 +844,7 @@ Then as a part of the code of a custom :term:`locale negotiator`:
.. code-block:: python
:linenos:
-
+
from pyramid.threadlocal import get_current_registry
settings = get_current_registry().settings
languages = settings['available_languages'].split()
@@ -897,7 +897,7 @@ application startup. For example:
:linenos:
from pyramid.config import Configurator
- config.add_translation_dirs('my.application:locale/',
+ config.add_translation_dirs('my.application:locale/',
'another.application:locale/')
A message catalog in a translation directory added via
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index c1558266a..631412f42 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -23,7 +23,7 @@ what type of application you're trying to construct.
These scaffolds are rendered using the :term:`PasteDeploy` ``paster`` script.
.. index::
- single: scaffolds
+ single: scaffolds
single: pyramid_starter scaffold
single: pyramid_zodb scaffold
single: pyramid_alchemy scaffold
@@ -55,7 +55,7 @@ The included scaffolds are these:
``pyramid_zodb``
URL mapping via :term:`traversal` and persistence via :term:`ZODB`.
-``pyramid_routesalchemy``
+``pyramid_routesalchemy``
URL mapping via :term:`URL dispatch` and persistence via
:term:`SQLAlchemy`
@@ -281,7 +281,7 @@ name ``MyProject`` as a section name:
.. code-block:: text
[chrism@vitaminf shellenv]$ ../bin/paster pshell development.ini MyProject
- Python 2.4.5 (#1, Aug 29 2008, 12:27:37)
+ Python 2.4.5 (#1, Aug 29 2008, 12:27:37)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help" for more information. "root" is the Pyramid app root object,
"registry" is the Pyramid registry object.
@@ -335,7 +335,7 @@ example, if you have the following ``.ini`` file content:
default_locale_name = en
[pipeline:main]
- pipeline =
+ pipeline =
egg:WebError#evalerror
MyProject
@@ -606,7 +606,7 @@ for each request.
.. note::
- In general, :app:`Pyramid` applications generated from scaffolds
+ In general, :app:`Pyramid` applications generated from scaffolds
should be threading-aware. It is not required that a :app:`Pyramid`
application be nonblocking as all application code will run in its own
thread, provided by the server you're using.
@@ -622,13 +622,13 @@ implementations.
Such a section should consists of global parameters that are shared by all
the applications, servers and :term:`middleware` defined within the
configuration file. The values in a ``[DEFAULT]`` section will be passed
- to your application's ``main`` function as ``global_values`` (see
+ to your application's ``main`` function as ``global_config`` (see
the reference to the ``main`` function in :ref:`init_py`).
``production.ini``
~~~~~~~~~~~~~~~~~~~
-The ``development.ini`` file is a :term:`PasteDeploy` configuration file with
+The ``production.ini`` file is a :term:`PasteDeploy` configuration file with
a purpose much like that of ``development.ini``. However, it disables the
WebError interactive debugger, replacing it with a logger which outputs
exception messages to ``stderr`` by default. It also turns off template
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index c4a37c23d..18cc8e539 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -218,11 +218,11 @@ You can configure a view to use the JSON renderer by naming ``json`` as the
.. code-block:: python
:linenos:
- config.add_view('myproject.views.hello_world',
+ config.add_view('myproject.views.hello_world',
name='hello',
context='myproject.resources.Hello',
renderer='json')
-
+
Views which use the JSON renderer can vary non-body response attributes by
using the api of the ``request.response`` attribute. See
@@ -246,7 +246,7 @@ See :ref:`chameleon_zpt_templates` for more information about ZPT templates.
If the ``renderer`` attribute of a view configuration is an absolute path or
a :term:`asset specification` which has a final path element with a filename
extension of ``.txt``, the :term:`Chameleon` text renderer is used. See
-:ref:`chameleon_zpt_templates` for more information about Chameleon text
+:ref:`chameleon_text_templates` for more information about Chameleon text
templates.
The behavior of these renderers is the same, except for the engine
@@ -264,7 +264,7 @@ dictionary, an error will be raised.
Before passing keywords to the template, the keyword arguments derived from
the dictionary returned by the view are augmented. The callable object --
-whatever object was used to define the ``view`` -- will be automatically
+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 into the keywords passed to
@@ -311,7 +311,7 @@ the API of the ``request.response`` attribute. See
The ``Mako`` template renderer renders views using a Mako template. When
used, the view must return a Response object or a Python *dictionary*. The
dictionary items will then be used in the global template space. If the view
-callable returns anything but a Response object, or a dictionary, an error
+callable returns anything but a Response object or a dictionary, an error
will be raised.
When using a ``renderer`` argument to a :term:`view configuration` to specify
@@ -498,15 +498,15 @@ following interface:
class RendererFactory:
def __init__(self, info):
- """ Constructor: info will be an object having the the
+ """ Constructor: info will be an object having the
following attributes: name (the renderer name), package
(the package that was 'current' at the time the
renderer was registered), type (the renderer type
name), registry (the current application registry) and
- settings (the deployment settings dictionary). """
+ settings (the deployment settings dictionary). """
def __call__(self, value, system):
- """ Call a the renderer implementation with the value
+ """ Call the renderer implementation with the value
and the system value passed in as arguments and return
the result (a string or unicode object). The value is
the return value of a view. The system value is a
@@ -518,7 +518,7 @@ factory constructor is available as :class:`pyramid.interfaces.IRendererInfo`.
There are essentially two different kinds of renderer factories:
-- A renderer factory which expects to accept a :term:`asset
+- A renderer factory which expects to accept an :term:`asset
specification`, or an absolute path, as the ``name`` attribute of the
``info`` object fed to its constructor. These renderer factories are
registered with a ``name`` value that begins with a dot (``.``). These
@@ -579,7 +579,7 @@ factory, which expects to be passed a filesystem path:
.. code-block:: python
:linenos:
- config.add_renderer(name='.jinja2',
+ config.add_renderer(name='.jinja2',
factory='my.package.MyJinja2Renderer')
Adding the above code to your application startup will allow you to use the
@@ -609,7 +609,7 @@ to the ``MyJinja2Renderer`` constructor will be the full value that was
set as ``renderer=`` in the view configuration.
Changing an Existing Renderer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can associate more than one filename extension with the same existing
renderer implementation as necessary if you need to use a different file
@@ -676,9 +676,9 @@ sets an ``override_renderer`` attribute on the request itself, which is the
def set_xmlrpc_params(event):
request = event.request
if (request.content_type == 'text/xml'
- and request.method == 'POST'
- and not 'soapaction' in request.headers
- and not 'x-pyramid-avoid-xmlrpc' in request.headers):
+ and request.method == 'POST'
+ and not 'soapaction' in request.headers
+ and not 'x-pyramid-avoid-xmlrpc' in request.headers):
params, method = parse_xmlrpc_request(request)
request.xmlrpc_params, request.xmlrpc_method = params, method
request.is_xmlrpc = True
diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst
index a11466a87..fa8ccc549 100644
--- a/docs/narr/resources.rst
+++ b/docs/narr/resources.rst
@@ -91,9 +91,9 @@ Here's a sample resource tree, represented by a variable named ``root``:
root = Resource({'a':Resource({'b':Resource({'c':Resource()})})})
The resource tree we've created above is represented by a dictionary-like
-root object which has a single child named ``a``. ``a`` has a single child
-named ``b``, and ``b`` has a single child named ``c``, which has no children.
-It is therefore possible to access ``c`` like so:
+root object which has a single child named ``'a'``. ``'a'`` has a single child
+named ``'b'``, and ``'b'`` has a single child named ``'c'``, which has no
+children. It is therefore possible to access the ``'c'`` leaf resource like so:
.. code-block:: python
:linenos:
@@ -101,7 +101,7 @@ It is therefore possible to access ``c`` like so:
root['a']['b']['c']
If you returned the above ``root`` object from a :term:`root factory`, the
-path ``/a/b/c`` would find the ``c`` object in the resource tree as the
+path ``/a/b/c`` would find the ``'c'`` object in the resource tree as the
result of :term:`traversal`.
In this example, each of the resources in the tree is of the same class.
@@ -428,7 +428,7 @@ list, we will get:
.. code-block:: python
:linenos:
-
+
list(lineage(thing2))
[ <Thing object at thing2>, <Thing object at thing1> ]
@@ -437,8 +437,8 @@ resource it was passed unconditionally. Then, if the resource supplied a
``__parent__`` attribute, it returns the resource represented by
``resource.__parent__``. If *that* resource has a ``__parent__`` attribute,
return that resource's parent, and so on, until the resource being inspected
-either has no ``__parent__`` attribute or which has a ``__parent__``
-attribute of ``None``.
+either has no ``__parent__`` attribute or has a ``__parent__`` attribute of
+``None``.
See the documentation for :func:`pyramid.location.lineage` for more
information.
@@ -563,6 +563,7 @@ To do so, use the :func:`zope.interface.directlyProvides` function:
.. code-block:: python
:linenos:
+ import datetime
from zope.interface import directlyProvides
from zope.interface import Interface
@@ -587,6 +588,7 @@ the :func:`zope.interface.alsoProvides` function:
.. code-block:: python
:linenos:
+ import datetime
from zope.interface import alsoProvides
from zope.interface import directlyProvides
from zope.interface import Interface
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
index b3747be61..e1715dc25 100644
--- a/docs/narr/traversal.rst
+++ b/docs/narr/traversal.rst
@@ -12,7 +12,7 @@ file system. Traversal walks down the path until it finds a published
resource, analogous to a file system "directory" or "file". The
resource found as the result of a traversal becomes the
:term:`context` of the :term:`request`. Then, the :term:`view lookup`
-subsystem is used to find some view code willing "publish" this
+subsystem is used to find some view code willing to "publish" this
resource by generating a :term:`response`.
Using :term:`Traversal` to map a URL to code is optional. It is often
@@ -49,17 +49,17 @@ For example, if the path info sequence is ``['a', 'b', 'c']``:
can be configured to return whatever object is appropriate as the
traversal root of your application.
-- Next, the first element (``a``) is popped from the path segment
+- Next, the first element (``'a'``) is popped from the path segment
sequence and is used as a key to lookup the corresponding resource
in the root. This invokes the root resource's ``__getitem__`` method
- using that value (``a``) as an argument.
+ using that value (``'a'``) as an argument.
-- If the root resource "contains" a resource with key ``a``, its
+- If the root resource "contains" a resource with key ``'a'``, its
``__getitem__`` method will return it. The :term:`context` temporarily
becomes the "A" resource.
-- The next segment (``b``) is popped from the path sequence, and the "A"
- resource's ``__getitem__`` is called with that value (``b``) as an
+- The next segment (``'b'``) is popped from the path sequence, and the "A"
+ resource's ``__getitem__`` is called with that value (``'b'``) as an
argument; we'll presume it succeeds.
- The "A" resource's ``__getitem__`` returns another resource, which
@@ -78,7 +78,7 @@ The results of a :term:`traversal` also include a :term:`view name`. If
traversal ends before the path segment sequence is exhausted, the
:term:`view name` is the *next* remaining path segment element. If the
:term:`traversal` expends all of the path segments, then the :term:`view
-name` is the empty string (`''`).
+name` is the empty string (``''``).
The combination of the context resource and the :term:`view name` found
via traversal is used later in the same request by the :term:`view
@@ -263,26 +263,26 @@ system uses this algorithm to find a :term:`context` resource and a
UTF-8 encoding. If any URL-unquoted path segment in ``PATH_INFO`` is not
decodeable using the UTF-8 decoding, a :exc:`TypeError` is raised. A
segment will be fully URL-unquoted and UTF8-decoded before it is passed
- it to the ``__getitem__`` of any resource during traversal.
+ in to the ``__getitem__`` of any resource during traversal.
Thus, a request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the
traversal sequence ``[u'a', u'b', u'c']``.
#. :term:`Traversal` begins at the root resource returned by the root
factory. For the traversal sequence ``[u'a', u'b', u'c']``, the root
- resource's ``__getitem__`` is called with the name ``a``. Traversal
+ resource's ``__getitem__`` is called with the name ``'a'``. Traversal
continues through the sequence. In our example, if the root resource's
``__getitem__`` called with the name ``a`` returns a resource (aka
- "resource ``a``"), that resource's ``__getitem__`` is called with the
- name ``b``. If resource A returns a resource when asked for ``b``,
- "resource ``b``"'s ``__getitem__`` is then asked for the name ``c``, and
- may return "resource ``c``".
+ resource "A"), that resource's ``__getitem__`` is called with the name
+ ``'b'``. If resource "A" returns a resource "B" when asked for ``'b'``,
+ resource B's ``__getitem__`` is then asked for the name ``'c'``, and may
+ return resource "C".
#. Traversal ends when a) the entire path is exhausted or b) when any
resouce raises a :exc:`KeyError` from its ``__getitem__`` or c) when any
non-final path element traversal does not have a ``__getitem__`` method
- (resulting in a :exc:`NameError`) or d) when any path element is prefixed
- with the set of characters ``@@`` (indicating that the characters
+ (resulting in a :exc:`AttributeError`) or d) when any path element is
+ prefixed with the set of characters ``@@`` (indicating that the characters
following the ``@@`` token should be treated as a :term:`view name`).
#. When traversal ends for any of the reasons in the previous step, the last
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index d99e5bed5..5640800a2 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -77,7 +77,7 @@ All forms of view configuration accept the same general types of arguments.
Many arguments supplied during view configuration are :term:`view predicate`
arguments. View predicate arguments used during view configuration are used
to narrow the set of circumstances in which :term:`view lookup` will find a
-particular view callable.
+particular view callable.
In general, the fewer number of predicates which are supplied to a
particular view configuration, the more likely it is that the associated
@@ -112,7 +112,7 @@ Non-Predicate Arguments
The name of a :term:`permission` that the user must possess in order to
invoke the :term:`view callable`. See :ref:`view_security_section` for
more information about view security and permissions.
-
+
If ``permission`` is not supplied, no permission is registered for this
view (it's accessible by any caller).
@@ -183,7 +183,7 @@ Non-Predicate Arguments
argument. The view callable it is passed will accept ``(context,
request)``. The decorator must return a replacement view callable which
also accepts ``(context, request)``.
-
+
``mapper``
A Python object or :term:`dotted Python name` which refers to a :term:`view
mapper`, or ``None``. By default it is ``None``, which indicates that the
@@ -228,7 +228,7 @@ configured view.
``pattern``, representing a part of the path that will be used by
:term:`traversal` against the result of the route's :term:`root factory`.
- If ``route_name`` is not supplied, the view callable will be have a chance
+ If ``route_name`` is not supplied, the view callable will only have a chance
of being invoked if no other route was matched. This is when the
request/context pair found via :term:`resource location` does not indicate
it matched any configured route.
@@ -400,7 +400,7 @@ configuration stanza:
.. code-block:: python
:linenos:
- config.add_view('mypackage.views.my_view', name='my_view', request_method='POST',
+ config.add_view('mypackage.views.my_view', name='my_view', request_method='POST',
context=MyResource, permission='read')
All arguments to ``view_config`` may be omitted. For example:
@@ -517,7 +517,7 @@ registration. For example:
This registers the same view under two different names.
-The decorator can also be used against class methods:
+The decorator can also be used against a method of a class:
.. code-block:: python
:linenos:
@@ -533,9 +533,9 @@ The decorator can also be used against class methods:
def amethod(self):
return Response('hello')
-When the decorator is used against a class method, a view is registered for
-the *class*, so the class constructor must accept an argument list in one of
-two forms: either it must accept a single argument ``request`` or it must
+When the decorator is used against a method of a class, a view is registered
+for the *class*, so the class constructor must accept an argument list in one
+of two forms: either it must accept a single argument ``request`` or it must
accept two arguments, ``context, request``.
The method which is decorated must return a :term:`response`.
@@ -760,7 +760,7 @@ Here is an example for a simple view configuration using :term:`traversal`:
URL = /FrontPage
context: <tutorial.models.Page object at 0xa12536c>
- view name:
+ view name:
View:
-----
@@ -791,7 +791,7 @@ A more complex configuration might generate something like this:
route name: about
route pattern: /about
route path: /about
- subpath:
+ subpath:
route predicates (request method = GET)
View:
@@ -805,7 +805,7 @@ A more complex configuration might generate something like this:
route name: about_post
route pattern: /about
route path: /about
- subpath:
+ subpath:
route predicates (request method = POST)
View:
diff --git a/docs/narr/zca.rst b/docs/narr/zca.rst
index 19c52d0c9..a99fd8b24 100644
--- a/docs/narr/zca.rst
+++ b/docs/narr/zca.rst
@@ -38,10 +38,10 @@ code is high.
While the ZCA is an excellent tool with which to build a *framework*
such as :app:`Pyramid`, it is not always the best tool with which
to build an *application* due to the opacity of the ``zope.component``
-APIs. Accordingly, :app:`Pyramid` tends to hide the the presence
-of the ZCA from application developers. You needn't understand the
-ZCA to create a :app:`Pyramid` application; its use is effectively
-only a framework implementation detail.
+APIs. Accordingly, :app:`Pyramid` tends to hide the presence of the
+ZCA from application developers. You needn't understand the ZCA to
+create a :app:`Pyramid` application; its use is effectively only a
+framework implementation detail.
However, developers who are already used to writing :term:`Zope`
applications often still wish to use the ZCA while building a
diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst
index 523aef8a8..6e3e4ce37 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -109,7 +109,7 @@ commands and files.
<Directory /Users/chrism/modwsgi/env>
WSGIProcessGroup pyramid
- Order allow, deny
+ Order allow,deny
Allow from all
</Directory>
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index de5c9486d..46c953f6d 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -32,10 +32,17 @@ Adding Authentication and Authorization Policies
We'll change our package's ``__init__.py`` file to enable an
``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable
-declarative security checking. When you're done, your ``__init__.py`` will
-look like so:
+declarative security checking. We need to import the new policies:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 4-5,8
+ :linenos:
+ :language: python
+
+Then, we'll add those policies to the configuration:
.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 16-18,26-28
:linenos:
:language: python
@@ -46,23 +53,30 @@ by this policy: it is required. The ``callback`` is a reference to a
``groupfinder`` function in the ``tutorial`` package's ``security.py`` file.
We haven't added that module yet, but we're about to.
+When you're done, your ``__init__.py`` will
+look like so:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :linenos:
+ :language: python
+
Adding ``security.py``
~~~~~~~~~~~~~~~~~~~~~~
Add a ``security.py`` module within your package (in the same
-directory as ``__init__.py``, ``views.py``, etc) with the following
+directory as ``__init__.py``, ``views.py``, etc.) with the following
content:
.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
:language: python
-The ``groupfinder`` function defined here is an authorization policy
+The ``groupfinder`` function defined here is an :term:`authentication policy`
"callback"; it is a callable that accepts a userid and a request. If the
-userid exists in the set of users known by the system, the callback will
+userid exists in the system, the callback will
return a sequence of group identifiers (or an empty sequence if the user
isn't a member of any groups). If the userid *does not* exist in the system,
-the callback will return ``None``. In a production system this data will
+the callback will return ``None``. In a production system, user and group data will
most often come from a database, but here we use "dummy" data to represent
user and groups sources. Note that the ``editor`` user is a member of the
``group:editors`` group in our dummy group data (the ``GROUPS`` data
@@ -158,7 +172,7 @@ into its template. We'll add something like this to each view body:
logged_in = authenticated_userid(request)
We'll then change the return value of each view that has an associated
-``renderer`` to pass the `resulting `logged_in`` value to the
+``renderer`` to pass the resulting ``logged_in`` value to the
template. For example:
.. ignore-next-block
@@ -277,7 +291,7 @@ as follows:
credentials with the username ``editor``, password ``editor`` will
show the edit page form being displayed.
-- After logging in (as a result of hitting an edit or add page and
+- After logging in (as a result of hitting an edit or add page and
submitting the login form with the ``editor`` credentials), we'll see
a Logout link in the upper right hand corner. When we click it,
we're logged out, and redirected back to the front page.
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index 92a3da09c..b111c9b4a 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -23,7 +23,7 @@ assumed to return a :term:`response` object.
the request as a single argument, you can obtain it via
``request.context``.
-We're going to define several :term:`view callable` functions then wire them
+We're going to define several :term:`view callable` functions, then wire them
into :app:`Pyramid` using some :term:`view configuration`.
The source code for this tutorial stage can be browsed via
@@ -204,8 +204,8 @@ the form post view callable for the form it renders. The ``context`` of the
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
-simply renders the edit form, passing the request, the page resource, and a
-save_url which will be used as the action of the generated form.
+simply renders the edit form, passing the page resource, and a ``save_url``
+which will be used as the action of the generated form.
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), the view grabs the
diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst
index c984c4f01..3edc6ba04 100644
--- a/docs/tutorials/wiki/index.rst
+++ b/docs/tutorials/wiki/index.rst
@@ -11,8 +11,8 @@ authentication.
For cut and paste purposes, the source code for all stages of this
tutorial can be browsed at
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki>`_.
+`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/
+<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/>`_.
.. toctree::
:maxdepth: 2
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index f4fb4323c..30fb67441 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -122,7 +122,7 @@ Preparation, Windows
.. code-block:: text
- c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 \
+ c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 ^
repoze.zodbconn nose coverage
.. _making_a_project:
@@ -234,7 +234,7 @@ On Windows:
.. code-block:: text
- c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \
+ c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^
--cover-erase --with-coverage
Looks like the code in the ``pyramid_zodb`` scaffold for ZODB projects is
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 3e9266754..f7dab5f47 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -16,8 +16,8 @@ def main(global_config, **settings):
authn_policy = AuthTktAuthenticationPolicy(secret='sosecret',
callback=groupfinder)
authz_policy = ACLAuthorizationPolicy()
- zodb_uri = settings.get('zodb_uri')
- if zodb_uri is None:
+ zodb_uri = settings.get('zodb_uri', False)
+ if zodb_uri is False:
raise ValueError("No 'zodb_uri' in application configuration.")
finder = PersistentApplicationFinder(zodb_uri, appmaker)
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index a9f776980..6a4093a3b 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -5,8 +5,8 @@ from tutorial.models import appmaker
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- zodb_uri = settings.get('zodb_uri')
- if zodb_uri is None:
+ zodb_uri = settings.get('zodb_uri', False)
+ if zodb_uri is False:
raise ValueError("No 'zodb_uri' in application configuration.")
finder = PersistentApplicationFinder(zodb_uri, appmaker)
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index bf0f683bf..73fc81d23 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -5,8 +5,8 @@ from tutorial.models import appmaker
def main(global_config, **settings):
""" This function returns a WSGI application.
"""
- zodb_uri = settings.get('zodb_uri')
- if zodb_uri is None:
+ zodb_uri = settings.get('zodb_uri', False)
+ if zodb_uri is False:
raise ValueError("No 'zodb_uri' in application configuration.")
finder = PersistentApplicationFinder(zodb_uri, appmaker)
diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py
index d9ff866f1..0ce5ea718 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py
@@ -139,16 +139,20 @@ class FunctionalTests(unittest.TestCase):
self.tmpdir = tempfile.mkdtemp()
dbpath = os.path.join( self.tmpdir, 'test.db')
- settings = { 'zodb_uri' : 'file://' + dbpath }
+ from repoze.zodbconn.uri import db_from_uri
+ db = db_from_uri('file://' + dbpath)
+ settings = { 'zodb_uri' : None }
app = main({}, **settings)
- from repoze.zodbconn.middleware import EnvironmentDeleterMiddleware
- app = EnvironmentDeleterMiddleware(app)
+ from repoze.zodbconn.connector import Connector
+ app = Connector(app, db)
+ self.db = db
from webtest import TestApp
self.testapp = TestApp(app)
def tearDown(self):
import shutil
+ self.db.close()
shutil.rmtree( self.tmpdir )
def test_root(self):
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index 91f7c2624..04a01fead 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -5,8 +5,8 @@ from tutorial.models import appmaker
def main(global_config, **settings):
""" This function returns a WSGI application.
"""
- zodb_uri = settings.get('zodb_uri')
- if zodb_uri is None:
+ zodb_uri = settings.get('zodb_uri', False)
+ if zodb_uri is False:
raise ValueError("No 'zodb_uri' in application configuration.")
finder = PersistentApplicationFinder(zodb_uri, appmaker)
diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst
index f3151dbcc..c843a0129 100644
--- a/docs/tutorials/wiki/tests.rst
+++ b/docs/tutorials/wiki/tests.rst
@@ -73,6 +73,6 @@ The expected result looks something like:
.........
----------------------------------------------------------------------
- Ran 9 tests in 0.203s
+ Ran 23 tests in 1.653s
OK
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index b1d3b0001..76ce4b83f 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -9,10 +9,23 @@ view, edit, and add pages to our wiki. For purposes of demonstration
we'll change our application to allow only people whom possess a
specific username (`editor`) to add and edit wiki pages but we'll
continue allowing anyone with access to the server to view pages.
-:app:`Pyramid` provides facilities for *authorization* and
-*authentication*. We'll make use of both features to provide security
+:app:`Pyramid` provides facilities for :term:`authorization` and
+:term:`authentication`. We'll make use of both features to provide security
to our application.
+We will add an :term:`authentication policy` and an
+:term:`authorization policy` to our :term:`application
+registry`, add a ``security.py`` module, create a :term:`root factory`
+with an :term:`ACL`, and add :term:`permission` declarations to
+the ``edit_page`` and ``add_page`` views.
+
+Then we will add ``login`` and ``logout`` views, and modify the
+existing views to make them return a ``logged_in`` flag to the
+renderer.
+
+Finally, we will add a ``login.pt`` template and change the existing
+``view.pt`` and ``edit.pt`` to show a "Logout" link when not logged in.
+
The source code for this tutorial stage can be browsed at
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/authorization/
<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/authorization/>`_.
@@ -63,7 +76,7 @@ information about what an :term:`ACL` represents.
:meth:`pyramid.config.Configurator.add_route` for more info.
We'll pass the ``RootFactory`` we created in the step above in as the
-``root_factory`` argument to a :term:`Configurator`.
+``root_factory`` argument to a :term:`Configurator`.
Configuring an Authorization Policy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -103,7 +116,7 @@ We'll also change ``__init__.py``, adding a call to
:term:`view callable`. This is also known as a :term:`forbidden view`:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 41-43
+ :lines: 25,41-43
:linenos:
:language: python
@@ -147,16 +160,16 @@ and adding views, your application's ``__init__.py`` will look like this:
:language: python
Adding ``security.py``
-~~~~~~~~~~~~~~~~~~~~~~
+----------------------
Add a ``security.py`` module within your package (in the same directory as
-:file:`__init__.py`, :file:`views.py`, etc) with the following content:
+:file:`__init__.py`, :file:`views.py`, etc.) with the following content:
.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
:language: python
-The groupfinder defined here is an :term:`authentication policy`
+The ``groupfinder`` function defined here is an :term:`authentication policy`
"callback"; it is a callable that accepts a userid and a request. If
the userid exists in the system, the callback will return a sequence
of group identifiers (or an empty sequence if the user isn't a member
@@ -176,7 +189,7 @@ and the permission associated with the ``add_page`` and ``edit_page``
views, the ``editor`` user should be able to add and edit pages.
Adding Login and Logout Views
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-----------------------------
We'll add a ``login`` view callable which renders a login form and
processes the post from the login form, checking credentials.
@@ -195,7 +208,7 @@ content:
:language: python
Changing Existing Views
-~~~~~~~~~~~~~~~~~~~~~~~
+-----------------------
Then we need to change each of our ``view_page``, ``edit_page`` and
``add_page`` views in ``views.py`` to pass a "logged in" parameter to its
@@ -221,7 +234,7 @@ We'll then change the return value of these views to pass the `resulting
edit_url = edit_url)
Adding the ``login.pt`` Template
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+--------------------------------
Add a ``login.pt`` template to your templates directory. It's
referred to within the login view we just added to ``login.py``.
@@ -230,7 +243,7 @@ referred to within the login view we just added to ``login.py``.
:language: xml
Change ``view.pt`` and ``edit.pt``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+----------------------------------
We'll also need to change our ``edit.pt`` and ``view.pt`` templates to
display a "Logout" link if someone is logged in. This link will
@@ -245,6 +258,25 @@ class="app-welcome align-right">`` div:
<a href="${request.application_url}/logout">Logout</a>
</span>
+Seeing Our Changes To ``views.py`` and our Templates
+----------------------------------------------------
+
+Our ``views.py`` module will look something like this when we're done:
+
+.. literalinclude:: src/authorization/tutorial/views.py
+ :linenos:
+ :language: python
+
+Our ``edit.pt`` template will look something like this when we're done:
+
+.. literalinclude:: src/authorization/tutorial/templates/edit.pt
+ :language: xml
+
+Our ``view.pt`` template will look something like this when we're done:
+
+.. literalinclude:: src/authorization/tutorial/templates/view.pt
+ :language: xml
+
Viewing the Application in a Browser
------------------------------------
@@ -272,31 +304,8 @@ try are as follows:
credentials with the username ``editor``, password ``editor`` will
display the edit page form.
-Seeing Our Changes To ``views.py`` and our Templates
-----------------------------------------------------
-
-Our ``views.py`` module will look something like this when we're done:
-
-.. literalinclude:: src/authorization/tutorial/views.py
- :linenos:
- :language: python
-
-Our ``edit.pt`` template will look something like this when we're done:
-
-.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :language: xml
-
-Our ``view.pt`` template will look something like this when we're done:
-
-.. literalinclude:: src/authorization/tutorial/templates/view.pt
- :language: xml
-
-Revisiting the Application
----------------------------
-
-When we revisit the application in a browser, and log in (as a result
-of hitting an edit or add page and submitting the login form with the
-``editor`` credentials), we'll see a Logout link in the upper right
-hand corner. When we click it, we're logged out, and redirected back
-to the front page.
-
+- After logging in (as a result of hitting an edit or add page
+ and submitting the login form with the ``editor``
+ credentials), we'll see a Logout link in the upper right hand
+ corner. When we click it, we're logged out, and redirected
+ back to the front page.
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 82e112c64..6151e0e25 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -92,7 +92,7 @@ use :meth:`pyramid.config.Configurator.add_view` in :term:`URL dispatch` to
register views for the routes, mapping your patterns to code:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 14
+ :lines: 14-15
:language: py
The first positional ``add_view`` argument ``tutorial.views.my_view`` is the
@@ -102,7 +102,7 @@ which returns a response or a dictionary. This view also names a
``renderer``, which is a template which lives in the ``templates``
subdirectory of the package. When the ``tutorial.views.my_view`` view
returns a dictionary, a :term:`renderer` will use this template to create a
-response. This
+response.
Finally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app`
method to return a :term:`WSGI` application:
@@ -133,7 +133,7 @@ Let's take a look. First, we need some imports to support later code.
:linenos:
:language: py
-Next we set up a SQLAlchemy "DBSession" object:
+Next we set up a SQLAlchemy "DBSession" object:
.. literalinclude:: src/basiclayout/tutorial/models.py
:lines: 15-16
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst
index e5d283125..7aa2214fc 100644
--- a/docs/tutorials/wiki2/definingmodels.rst
+++ b/docs/tutorials/wiki2/definingmodels.rst
@@ -45,7 +45,7 @@ SQLAlchemy models are easier to use than directly-mapped ones.
:language: python
As you can see, our ``Page`` class has a class level attribute
-``__tablename__`` which equals the string ``pages``. This means that
+``__tablename__`` which equals the string ``'pages'``. This means that
SQLAlchemy will store our wiki data in a SQL table named ``pages``. Our Page
class will also have class-level attributes named ``id``, ``name`` and
``data`` (all instances of :class:`sqlalchemy.Column`). These will map to
@@ -67,7 +67,7 @@ Here, we're using a slightly different binding syntax. It is otherwise
largely the same as the ``initialize_sql`` in the paster-generated
``models.py``.
-Our DBSession assignment stays the same as the original generated
+Our ``DBSession`` assignment stays the same as the original generated
``models.py``.
Looking at the Result of all Our Edits to ``models.py``
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index 43cbc3483..c91d1d914 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -25,9 +25,9 @@ match has an attribute named ``matchdict`` that contains the elements placed
into the URL by the ``pattern`` of a ``route`` statement. For instance, if a
call to :meth:`pyramid.config.Configurator.add_route` in ``__init__.py`` had
the pattern ``{one}/{two}``, and the URL at ``http://example.com/foo/bar``
-was invoked, matching this pattern, 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``.
+was invoked, matching this pattern, 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
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/
@@ -80,9 +80,9 @@ to be edited. We'll describe each one briefly and show the resulting
The ``view_wiki`` view function
-------------------------------
-The ``view_wiki`` function will respond as the :term:`default view` of a
-``Wiki`` model object. It always redirects to a URL which represents the
-path to our "FrontPage".
+The ``view_wiki`` function is the :term:`default view` that will be called
+when a request is made to the root URL of our wiki. It always redirects to
+a URL which represents the path to our "FrontPage".
.. literalinclude:: src/views/tutorial/views.py
:pyobject: view_wiki
@@ -100,11 +100,10 @@ page (e.g. ``http://localhost:6543/FrontPage``), and will use it as the
The ``view_page`` view function
-------------------------------
-The ``view_page`` function will respond as the :term:`default view` of
-a ``Page`` object. The ``view_page`` function renders the
-:term:`ReStructuredText` body of a page (stored as the ``data``
-attribute of a Page object) as HTML. Then it substitutes an HTML
-anchor for each *WikiWord* reference in the rendered HTML using a
+The ``view_page`` function will be used to show a single page of our
+wiki. It renders the :term:`ReStructuredText` body of a page (stored as
+the ``data`` attribute of a Page object) as HTML. Then it substitutes an
+HTML anchor for each *WikiWord* reference in the rendered HTML using a
compiled regular expression.
.. literalinclude:: src/views/tutorial/views.py
@@ -147,15 +146,15 @@ will have the values we need to construct URLs and find model objects.
:linenos:
:language: python
-The matchdict will have a ``pagename`` key that matches the name of the page
-we'd like to add. If our add view is invoked via,
-e.g. ``http://localhost:6543/add_page/SomeName``, the ``pagename`` value in
-the matchdict will be ``SomeName``.
+The ``matchdict`` will have a ``'pagename'`` key that matches the name of
+the page we'd like to add. If our add view is invoked via,
+e.g. ``http://localhost:6543/add_page/SomeName``, the value for
+``'pagename'`` in the ``matchdict`` will be ``'SomeName'``.
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
callable renders a template. To do so, it generates a "save url" which the
-template use as the form post URL during rendering. We're lazy here, so
+template uses as the form post URL during rendering. We're lazy here, so
we're trying to use the same template (``templates/edit.pt``) for the add
view as well as the page edit view, so we create a dummy Page object in order
to satisfy the edit form's desire to have *some* page object exposed as
@@ -164,10 +163,10 @@ view to a response.
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), we scrape the page body
-from the form data, create a Page object using the name in the matchdict
-``pagename``, and obtain the page body from the request, and save it into the
-database using ``session.add``. We then redirect back to the ``view_page``
-view (the :term:`default view` for a Page) for the newly created page.
+from the form data, create a Page object with this page body and the name
+taken from ``matchdict['pagename']``, and save it into the database using
+``session.add``. We then redirect back to the ``view_page`` view for the
+newly created page.
The ``edit_page`` view function
-------------------------------
@@ -175,7 +174,7 @@ The ``edit_page`` view function
The ``edit_page`` function will be invoked when a user clicks the "Edit this
Page" button on the view form. It renders an edit form but it also acts as
the handler for the form it renders. The ``matchdict`` attribute of the
-request passed to the ``edit_page`` view will have a ``pagename`` key
+request passed to the ``edit_page`` view will have a ``'pagename'`` key
matching the name of the page the user wants to edit.
.. literalinclude:: src/views/tutorial/views.py
@@ -185,14 +184,14 @@ matching the name of the page the user wants to edit.
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
-simply renders the edit form, passing the request, the page object, and a
-save_url which will be used as the action of the generated form.
+simply renders the edit form, passing the page object and a ``save_url``
+which will be used as the action of the generated form.
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), the view grabs the
-``body`` element of the request parameter and sets it as the ``data``
-attribute of the page object. It then redirects to the default view of the
-wiki page, which will always be the ``view_page`` view.
+``body`` element of the request parameters and sets it as the ``data``
+attribute of the page object. It then redirects to the ``view_page`` view
+of the wiki page.
Viewing the Result of all Our Edits to ``views.py``
===================================================
@@ -275,7 +274,7 @@ Mapping Views to URLs in ``__init__.py``
The ``__init__.py`` file contains
:meth:`pyramid.config.Configurator.add_view` calls which serve to map
routes via :term:`url dispatch` to views. First, we’ll get rid of the
-existing route created by the template using the name ``home``. It’s only an
+existing route created by the template using the name ``'home'``. It’s only an
example and isn’t relevant to our application.
We then need to add four calls to ``add_route``. Note that the *ordering* of
@@ -283,7 +282,7 @@ these declarations is very important. ``route`` declarations are matched in
the order they're found in the ``__init__.py`` file.
#. Add a declaration which maps the pattern ``/`` (signifying the root URL)
- to the route named ``view_wiki``.
+ to the route named ``view_wiki``.
#. Add a declaration which maps the pattern ``/{pagename}`` to the route named
``view_page``. This is the regular view for a page.
@@ -343,46 +342,3 @@ Exception('Forced Exception')``). Then visit the error-raising view
in a browser. You should see an interactive exception handler in the
browser which allows you to examine values in a post-mortem mode.
-Adding Tests
-============
-
-Since we've added a good bit of imperative code here, it's useful to
-define tests for the views we've created. We'll change our tests.py
-module to look like this:
-
-.. literalinclude:: src/views/tutorial/tests.py
- :linenos:
- :language: python
-
-We can then run the tests using something like:
-
-.. code-block:: text
- :linenos:
-
- $ python setup.py test -q
-
-The expected output is something like:
-
-.. code-block:: text
- :linenos:
-
- running test
- running egg_info
- writing requirements to tutorial.egg-info/requires.txt
- writing tutorial.egg-info/PKG-INFO
- writing top-level names to tutorial.egg-info/top_level.txt
- writing dependency_links to tutorial.egg-info/dependency_links.txt
- writing entry points to tutorial.egg-info/entry_points.txt
- unrecognized .svn/entries format in
- reading manifest file 'tutorial.egg-info/SOURCES.txt'
- writing manifest file 'tutorial.egg-info/SOURCES.txt'
- running build_ext
- ......
- ----------------------------------------------------------------------
- Ran 6 tests in 0.181s
-
- OK
-
-
-
-
diff --git a/docs/tutorials/wiki2/index.rst b/docs/tutorials/wiki2/index.rst
index 1aff949b9..d05d70f3c 100644
--- a/docs/tutorials/wiki2/index.rst
+++ b/docs/tutorials/wiki2/index.rst
@@ -11,8 +11,8 @@ basic Wiki application with authentication.
For cut and paste purposes, the source code for all stages of this
tutorial can be browsed at
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/>`_.
+`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/
+<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/>`_.
.. toctree::
:maxdepth: 2
@@ -23,6 +23,7 @@ tutorial can be browsed at
definingmodels
definingviews
authorization
+ tests
distributing
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index b5c73e9c5..bd597b5df 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -73,7 +73,7 @@ Preparation, Windows
.. code-block:: text
- c:\pyramidtut> Scripts\easy_install -i docutils \
+ c:\pyramidtut> Scripts\easy_install docutils ^
nose coverage zope.sqlalchemy SQLAlchemy repoze.tm2
@@ -205,7 +205,7 @@ On Windows:
.. code-block:: text
- c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \
+ c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^
--cover-erase --with-coverage
Looks like our package's ``models`` module doesn't quite have 100%
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py
index 487299c4c..53c6d1122 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py
@@ -26,16 +26,17 @@ class Page(Base):
data = Column(Text)
def __init__(self, name, data):
- self.name = name
- self.data = data
+ self.name = name
+ self.data = data
def initialize_sql(engine):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
Base.metadata.create_all(engine)
try:
+ transaction.begin()
session = DBSession()
- page = Page('FrontPage', 'initial data')
+ page = Page('FrontPage', 'This is the front page')
session.add(page)
transaction.commit()
except IntegrityError:
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
index 5abd8391e..e0b84971d 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -2,7 +2,7 @@ import re
from docutils.core import publish_parts
-from pyramid.httpexceptions import HTTPFound
+from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.security import authenticated_userid
from pyramid.url import route_url
@@ -19,7 +19,9 @@ def view_wiki(request):
def view_page(request):
pagename = request.matchdict['pagename']
session = DBSession()
- page = session.query(Page).filter_by(name=pagename).one()
+ page = session.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
@@ -51,7 +53,7 @@ def add_page(request):
page = Page('', '')
logged_in = authenticated_userid(request)
return dict(page=page, save_url=save_url, logged_in=logged_in)
-
+
def edit_page(request):
name = request.matchdict['pagename']
session = DBSession()
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py
index 797fff929..ecc8d567b 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/models.py
@@ -24,16 +24,17 @@ class Page(Base):
data = Column(Text)
def __init__(self, name, data):
- self.name = name
- self.data = data
+ self.name = name
+ self.data = data
def initialize_sql(engine):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
Base.metadata.create_all(engine)
try:
+ transaction.begin()
session = DBSession()
- page = Page('FrontPage', 'initial data')
+ page = Page('FrontPage', 'This is the front page')
session.add(page)
transaction.commit()
except IntegrityError:
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests.py b/docs/tutorials/wiki2/src/tests/tutorial/tests.py
new file mode 100644
index 000000000..98a4969e9
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests.py
@@ -0,0 +1,266 @@
+import unittest
+
+from pyramid import testing
+
+
+def _initTestingDB():
+ from tutorial.models import DBSession
+ from tutorial.models import Base
+ from sqlalchemy import create_engine
+ engine = create_engine('sqlite:///:memory:')
+ DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
+ Base.metadata.create_all(engine)
+ return DBSession
+
+def _registerRoutes(config):
+ config.add_route('view_page', '{pagename}')
+ config.add_route('edit_page', '{pagename}/edit_page')
+ config.add_route('add_page', 'add_page/{pagename}')
+
+
+class PageModelTests(unittest.TestCase):
+
+ def setUp(self):
+ self.session = _initTestingDB()
+
+ def tearDown(self):
+ self.session.remove()
+
+ def _getTargetClass(self):
+ from tutorial.models import Page
+ return Page
+
+ def _makeOne(self, name='SomeName', data='some data'):
+ return self._getTargetClass()(name, data)
+
+ def test_constructor(self):
+ instance = self._makeOne()
+ self.assertEqual(instance.name, 'SomeName')
+ self.assertEqual(instance.data, 'some data')
+
+class InitializeSqlTests(unittest.TestCase):
+
+ def setUp(self):
+ from tutorial.models import DBSession
+ DBSession.remove()
+
+ def tearDown(self):
+ from tutorial.models import DBSession
+ DBSession.remove()
+
+ def _callFUT(self, engine):
+ from tutorial.models import initialize_sql
+ return initialize_sql(engine)
+
+ def test_it(self):
+ from sqlalchemy import create_engine
+ engine = create_engine('sqlite:///:memory:')
+ self._callFUT(engine)
+ from tutorial.models import DBSession, Page
+ self.assertEqual(DBSession.query(Page).one().data,
+ 'This is the front page')
+
+class ViewWikiTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views import view_wiki
+ return view_wiki(request)
+
+ def test_it(self):
+ _registerRoutes(self.config)
+ request = testing.DummyRequest()
+ response = self._callFUT(request)
+ self.assertEqual(response.location, 'http://example.com/FrontPage')
+
+class ViewPageTests(unittest.TestCase):
+ def setUp(self):
+ self.session = _initTestingDB()
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ self.session.remove()
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views import view_page
+ return view_page(request)
+
+ def test_it(self):
+ from tutorial.models import Page
+ request = testing.DummyRequest()
+ request.matchdict['pagename'] = 'IDoExist'
+ page = Page('IDoExist', 'Hello CruelWorld IDoExist')
+ self.session.add(page)
+ _registerRoutes(self.config)
+ info = self._callFUT(request)
+ self.assertEqual(info['page'], page)
+ self.assertEqual(
+ info['content'],
+ '<div class="document">\n'
+ '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
+ 'CruelWorld</a> '
+ '<a href="http://example.com/IDoExist">'
+ 'IDoExist</a>'
+ '</p>\n</div>\n')
+ self.assertEqual(info['edit_url'],
+ 'http://example.com/IDoExist/edit_page')
+
+class AddPageTests(unittest.TestCase):
+ def setUp(self):
+ self.session = _initTestingDB()
+ self.config = testing.setUp()
+ self.config.begin()
+
+ def tearDown(self):
+ self.session.remove()
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views import add_page
+ return add_page(request)
+
+ def test_it_notsubmitted(self):
+ _registerRoutes(self.config)
+ request = testing.DummyRequest()
+ request.matchdict = {'pagename':'AnotherPage'}
+ info = self._callFUT(request)
+ self.assertEqual(info['page'].data,'')
+ self.assertEqual(info['save_url'],
+ 'http://example.com/add_page/AnotherPage')
+
+ def test_it_submitted(self):
+ from tutorial.models import Page
+ _registerRoutes(self.config)
+ request = testing.DummyRequest({'form.submitted':True,
+ 'body':'Hello yo!'})
+ request.matchdict = {'pagename':'AnotherPage'}
+ self._callFUT(request)
+ page = self.session.query(Page).filter_by(name='AnotherPage').one()
+ self.assertEqual(page.data, 'Hello yo!')
+
+class EditPageTests(unittest.TestCase):
+ def setUp(self):
+ self.session = _initTestingDB()
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ self.session.remove()
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views import edit_page
+ return edit_page(request)
+
+ def test_it_notsubmitted(self):
+ from tutorial.models import Page
+ _registerRoutes(self.config)
+ request = testing.DummyRequest()
+ request.matchdict = {'pagename':'abc'}
+ page = Page('abc', 'hello')
+ self.session.add(page)
+ info = self._callFUT(request)
+ self.assertEqual(info['page'], page)
+ self.assertEqual(info['save_url'],
+ 'http://example.com/abc/edit_page')
+
+ def test_it_submitted(self):
+ from tutorial.models import Page
+ _registerRoutes(self.config)
+ request = testing.DummyRequest({'form.submitted':True,
+ 'body':'Hello yo!'})
+ request.matchdict = {'pagename':'abc'}
+ page = Page('abc', 'hello')
+ self.session.add(page)
+ response = self._callFUT(request)
+ self.assertEqual(response.location, 'http://example.com/abc')
+ self.assertEqual(page.data, 'Hello yo!')
+
+class FunctionalTests(unittest.TestCase):
+
+ viewer_login = '/login?login=viewer&password=viewer' \
+ '&came_from=FrontPage&form.submitted=Login'
+ viewer_wrong_login = '/login?login=viewer&password=incorrect' \
+ '&came_from=FrontPage&form.submitted=Login'
+ editor_login = '/login?login=editor&password=editor' \
+ '&came_from=FrontPage&form.submitted=Login'
+
+ def setUp(self):
+ from tutorial import main
+ settings = { 'sqlalchemy.url': 'sqlite:///:memory:'}
+ app = main({}, **settings)
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+
+ def tearDown(self):
+ del self.testapp
+ from tutorial.models import DBSession
+ DBSession.remove()
+
+ def test_root(self):
+ res = self.testapp.get('/', status=302)
+ self.assertTrue(not res.body)
+
+ def test_FrontPage(self):
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue('FrontPage' in res.body)
+
+ def test_unexisting_page(self):
+ res = self.testapp.get('/SomePage', status=404)
+
+ def test_successful_log_in(self):
+ res = self.testapp.get(self.viewer_login, status=302)
+ self.assertTrue(res.location == 'FrontPage')
+
+ def test_failed_log_in(self):
+ res = self.testapp.get(self.viewer_wrong_login, status=200)
+ self.assertTrue('login' in res.body)
+
+ def test_logout_link_present_when_logged_in(self):
+ self.testapp.get(self.viewer_login, status=302)
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue('Logout' in res.body)
+
+ def test_logout_link_not_present_after_logged_out(self):
+ self.testapp.get(self.viewer_login, status=302)
+ self.testapp.get('/FrontPage', status=200)
+ res = self.testapp.get('/logout', status=302)
+ self.assertTrue('Logout' not in res.body)
+
+ def test_anonymous_user_cannot_edit(self):
+ res = self.testapp.get('/FrontPage/edit_page', status=200)
+ self.assertTrue('Login' in res.body)
+
+ def test_anonymous_user_cannot_add(self):
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue('Login' in res.body)
+
+ def test_viewer_user_cannot_edit(self):
+ self.testapp.get(self.viewer_login, status=302)
+ res = self.testapp.get('/FrontPage/edit_page', status=200)
+ self.assertTrue('Login' in res.body)
+
+ def test_viewer_user_cannot_add(self):
+ self.testapp.get(self.viewer_login, status=302)
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue('Login' in res.body)
+
+ def test_editors_member_user_can_edit(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/FrontPage/edit_page', status=200)
+ self.assertTrue('Editing' in res.body)
+
+ def test_editors_member_user_can_add(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue('Editing' in res.body)
+
+ def test_editors_member_user_can_view(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue('FrontPage' in res.body)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py
index 23b8afab8..960c14941 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/models.py
@@ -23,14 +23,15 @@ class Page(Base):
data = Column(Text)
def __init__(self, name, data):
- self.name = name
- self.data = data
+ self.name = name
+ self.data = data
def initialize_sql(engine):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
Base.metadata.create_all(engine)
try:
+ transaction.begin()
session = DBSession()
page = Page('FrontPage', 'initial data')
session.add(page)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py
index b8896abe7..f3d7f4a99 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views.py
@@ -2,7 +2,7 @@ import re
from docutils.core import publish_parts
-from pyramid.httpexceptions import HTTPFound
+from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.url import route_url
from tutorial.models import DBSession
@@ -16,9 +16,11 @@ def view_wiki(request):
pagename='FrontPage'))
def view_page(request):
- matchdict = request.matchdict
+ pagename = request.matchdict['pagename']
session = DBSession()
- page = session.query(Page).filter_by(name=matchdict['pagename']).one()
+ page = session.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
@@ -32,8 +34,7 @@ def view_page(request):
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
- edit_url = route_url('edit_page', request,
- pagename=matchdict['pagename'])
+ edit_url = route_url('edit_page', request, pagename=pagename)
return dict(page=page, content=content, edit_url=edit_url)
def add_page(request):
@@ -48,7 +49,7 @@ def add_page(request):
save_url = route_url('add_page', request, pagename=name)
page = Page('', '')
return dict(page=page, save_url=save_url)
-
+
def edit_page(request):
name = request.matchdict['pagename']
session = DBSession()
diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst
new file mode 100644
index 000000000..7a4e65529
--- /dev/null
+++ b/docs/tutorials/wiki2/tests.rst
@@ -0,0 +1,74 @@
+============
+Adding Tests
+============
+
+We will now add tests for the models and the views and a few functional
+tests in the ``tests.py``. Tests ensure that an application works, and
+that it continues to work after some changes are made in the future.
+
+Testing the Models
+==================
+
+We write a test class for the model class ``Page`` and another test class
+for the ``initialize_sql`` function.
+
+To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a
+result of the ``pyramid_routesalchemy`` project generator. We'll add two
+test classes: one for the ``Page`` model named ``PageModelTests``, one for the
+``initialize_sql`` function named ``InitializeSqlTests``.
+
+Testing the Views
+=================
+
+We'll modify our ``tests.py`` file, adding tests for each view function we
+added above. As a result, we'll *delete* the ``ViewTests`` test in the file,
+and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``,
+``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``,
+``view_page``, ``add_page``, and ``edit_page`` views respectively.
+
+Functional tests
+================
+
+We test the whole application, covering security aspects that are not
+tested in the unit tests, like logging in, logging out, checking that
+the ``viewer`` user cannot add or edit pages, but the ``editor`` user
+can, and so on.
+
+Viewing the results of all our edits to ``tests.py``
+====================================================
+
+Once we're done with the ``tests.py`` module, it will look a lot like the
+below:
+
+.. literalinclude:: src/tests/tutorial/tests.py
+ :linenos:
+ :language: python
+
+Running the Tests
+=================
+
+We can run these tests by using ``setup.py test`` in the same way we did in
+:ref:`running_tests`. Assuming our shell's current working directory is the
+"tutorial" distribution directory:
+
+On UNIX:
+
+.. code-block:: text
+
+ $ ../bin/python setup.py test -q
+
+On Windows:
+
+.. code-block:: text
+
+ c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
+
+The expected result looks something like:
+
+.. code-block:: text
+
+ ......................
+ ----------------------------------------------------------------------
+ Ran 22 tests in 2.700s
+
+ OK
diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst
index 533ae3637..172a20343 100644
--- a/docs/whatsnew-1.1.rst
+++ b/docs/whatsnew-1.1.rst
@@ -75,6 +75,10 @@ Default HTTP Exception View
Minor Feature Additions
-----------------------
+- New authentication policy:
+ :class:`pyramid.authentication.SessionAuthenticationPolicy`, which uses a
+ session to store credentials.
+
- A function named :func:`pyramid.httpexceptions.responsecode` is a shortcut
that can be used to create HTTP exception response objects using an HTTP
integer status code.
@@ -116,6 +120,15 @@ Minor Feature Additions
Deprecations and Behavior Differences
-------------------------------------
+- The default Mako renderer is now configured to escape all HTML in
+ expression tags. This is intended to help prevent XSS attacks caused by
+ rendering unsanitized input from users. To revert this behavior in user's
+ templates, they need to filter the expression through the 'n' filter::
+
+ ${ myhtml | n }.
+
+ See https://github.com/Pylons/pyramid/issues/193.
+
- Deprecated all assignments to ``request.response_*`` attributes (for
example ``request.response_content_type = 'foo'`` is now deprecated).
Assignments and mutations of assignable request attributes that were
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index a6c74e549..d42843528 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -524,4 +524,46 @@ class AuthTktCookieHelper(object):
cookie_value = ticket.cookie_value()
return self._get_cookies(environ, cookie_value, max_age)
-
+
+class SessionAuthenticationPolicy(CallbackAuthenticationPolicy):
+ """ A :app:`Pyramid` authentication policy which gets its data from the
+ configured :term:`session`. For this authentication policy to work, you
+ will have to follow the instructions in the :ref:`sessions_chapter` to
+ configure a :term:`session factory`.
+
+ Constructor Arguments
+
+ ``prefix``
+
+ A prefix used when storing the authentication parameters in the
+ session. Defaults to 'auth.'. Optional.
+
+ ``callback``
+
+ Default: ``None``. A callback passed the userid and the
+ request, expected to return ``None`` if the userid doesn't
+ exist or a sequence of principal identifiers (possibly empty) if
+ the user does exist. If ``callback`` is ``None``, the userid
+ will be assumed to exist with no principals. Optional.
+ """
+ implements(IAuthenticationPolicy)
+
+ def __init__(self, prefix='auth.', callback=None):
+ self.callback = callback
+ self.prefix = prefix or ''
+ self.userid_key = prefix + 'userid'
+
+ def remember(self, request, principal, **kw):
+ """ Store a principal in the session."""
+ request.session[self.userid_key] = principal
+ return []
+
+ def forget(self, request):
+ """ Remove the stored principal from the session."""
+ if self.userid_key in request.session:
+ del request.session[self.userid_key]
+ return []
+
+ def unauthenticated_userid(self, request):
+ return request.session.get(self.userid_key)
+
diff --git a/pyramid/i18n.py b/pyramid/i18n.py
index 4b8281673..7bc096bb1 100644
--- a/pyramid/i18n.py
+++ b/pyramid/i18n.py
@@ -151,11 +151,16 @@ def make_localizer(current_locale_name, translation_directories):
translations found in the list of translation directories."""
translations = Translations()
translations._catalog = {}
+
+ locales_to_try = [current_locale_name]
+ if '_' in current_locale_name:
+ locales_to_try.append(current_locale_name.split('_')[0])
+
for tdir in translation_directories:
locale_dirs = [ (lname, os.path.join(tdir, lname)) for lname in
os.listdir(tdir) ]
for locale_name, locale_dir in locale_dirs:
- if locale_name != current_locale_name:
+ if locale_name not in locales_to_try:
continue
messages_dir = os.path.join(locale_dir, 'LC_MESSAGES')
if not os.path.isdir(os.path.realpath(messages_dir)):
diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py
index 9d14ca8fe..fea8066d4 100644
--- a/pyramid/mako_templating.py
+++ b/pyramid/mako_templating.py
@@ -66,7 +66,7 @@ def renderer_factory(info):
module_directory = settings.get('mako.module_directory', None)
input_encoding = settings.get('mako.input_encoding', 'utf-8')
error_handler = settings.get('mako.error_handler', None)
- default_filters = settings.get('mako.default_filters', None)
+ default_filters = settings.get('mako.default_filters', 'h')
imports = settings.get('mako.imports', None)
strict_undefined = settings.get('mako.strict_undefined', 'false')
if directories is None:
diff --git a/pyramid/scaffolds/zodb/+package+/__init__.py_tmpl b/pyramid/scaffolds/zodb/+package+/__init__.py_tmpl
index 2fe3496aa..b5c9a2538 100644
--- a/pyramid/scaffolds/zodb/+package+/__init__.py_tmpl
+++ b/pyramid/scaffolds/zodb/+package+/__init__.py_tmpl
@@ -5,8 +5,8 @@ from {{package}}.models import appmaker
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- zodb_uri = settings.get('zodb_uri')
- if zodb_uri is None:
+ zodb_uri = settings.get('zodb_uri', False)
+ if zodb_uri is False:
raise ValueError("No 'zodb_uri' in application configuration.")
finder = PersistentApplicationFinder(zodb_uri, appmaker)
diff --git a/pyramid/settings.py b/pyramid/settings.py
index b1078f2ec..edea9ce99 100644
--- a/pyramid/settings.py
+++ b/pyramid/settings.py
@@ -78,7 +78,7 @@ def get_settings():
:func:`pyramid.router.make_app` API.
.. warning:: This method is deprecated as of Pyramid 1.0. Use
- ``pyramid.threadlocals.get_current_registry().settings`` instead or use
+ ``pyramid.threadlocal.get_current_registry().settings`` instead or use
the ``settings`` attribute of the registry available from the request
(``request.registry.settings``).
"""
diff --git a/pyramid/tests/fixtures/nonminimal.mak b/pyramid/tests/fixtures/nonminimal.mak
new file mode 100644
index 000000000..9de95ec92
--- /dev/null
+++ b/pyramid/tests/fixtures/nonminimal.mak
@@ -0,0 +1 @@
+Hello, ${name}!
diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py
index ecd76a71c..8acc2b31c 100644
--- a/pyramid/tests/test_authentication.py
+++ b/pyramid/tests/test_authentication.py
@@ -751,12 +751,106 @@ class TestAuthTktCookieHelper(unittest.TestCase):
'auth_tkt=""; Path=/; Domain=.localhost; Max-Age=0; '
'Expires=Wed, 31-Dec-97 23:59:59 GMT')
+
+class TestSessionAuthenticationPolicy(unittest.TestCase):
+ def _getTargetClass(self):
+ from pyramid.authentication import SessionAuthenticationPolicy
+ return SessionAuthenticationPolicy
+
+ def _makeOne(self, callback=None, prefix=''):
+ return self._getTargetClass()(prefix=prefix, callback=callback)
+
+ def test_class_implements_IAuthenticationPolicy(self):
+ from zope.interface.verify import verifyClass
+ from pyramid.interfaces import IAuthenticationPolicy
+ verifyClass(IAuthenticationPolicy, self._getTargetClass())
+
+ def test_instance_implements_IAuthenticationPolicy(self):
+ from zope.interface.verify import verifyObject
+ from pyramid.interfaces import IAuthenticationPolicy
+ verifyObject(IAuthenticationPolicy, self._makeOne())
+
+ def test_unauthenticated_userid_returns_None(self):
+ request = DummyRequest()
+ policy = self._makeOne()
+ self.assertEqual(policy.unauthenticated_userid(request), None)
+
+ def test_unauthenticated_userid(self):
+ request = DummyRequest(session={'userid':'fred'})
+ policy = self._makeOne()
+ self.assertEqual(policy.unauthenticated_userid(request), 'fred')
+
+ def test_authenticated_userid_no_cookie_identity(self):
+ request = DummyRequest()
+ policy = self._makeOne()
+ self.assertEqual(policy.authenticated_userid(request), None)
+
+ def test_authenticated_userid_callback_returns_None(self):
+ request = DummyRequest(session={'userid':'fred'})
+ def callback(userid, request):
+ return None
+ policy = self._makeOne(callback)
+ self.assertEqual(policy.authenticated_userid(request), None)
+
+ def test_authenticated_userid(self):
+ request = DummyRequest(session={'userid':'fred'})
+ def callback(userid, request):
+ return True
+ policy = self._makeOne(callback)
+ self.assertEqual(policy.authenticated_userid(request), 'fred')
+
+ def test_effective_principals_no_identity(self):
+ from pyramid.security import Everyone
+ request = DummyRequest()
+ policy = self._makeOne()
+ self.assertEqual(policy.effective_principals(request), [Everyone])
+
+ def test_effective_principals_callback_returns_None(self):
+ from pyramid.security import Everyone
+ request = DummyRequest(session={'userid':'fred'})
+ def callback(userid, request):
+ return None
+ policy = self._makeOne(callback)
+ self.assertEqual(policy.effective_principals(request), [Everyone])
+
+ def test_effective_principals(self):
+ from pyramid.security import Everyone
+ from pyramid.security import Authenticated
+ request = DummyRequest(session={'userid':'fred'})
+ def callback(userid, request):
+ return ['group.foo']
+ policy = self._makeOne(callback)
+ self.assertEqual(policy.effective_principals(request),
+ [Everyone, Authenticated, 'fred', 'group.foo'])
+
+ def test_remember(self):
+ request = DummyRequest()
+ policy = self._makeOne()
+ result = policy.remember(request, 'fred')
+ self.assertEqual(request.session.get('userid'), 'fred')
+ self.assertEqual(result, [])
+
+ def test_forget(self):
+ request = DummyRequest(session={'userid':'fred'})
+ policy = self._makeOne()
+ result = policy.forget(request)
+ self.assertEqual(request.session.get('userid'), None)
+ self.assertEqual(result, [])
+
+ def test_forget_no_identity(self):
+ request = DummyRequest()
+ policy = self._makeOne()
+ result = policy.forget(request)
+ self.assertEqual(request.session.get('userid'), None)
+ self.assertEqual(result, [])
+
class DummyContext:
pass
class DummyRequest:
- def __init__(self, environ):
- self.environ = environ
+ def __init__(self, environ=None, session=None):
+ self.environ = environ or {}
+ self.session = session or {}
self.callbacks = []
def add_response_callback(self, callback):
diff --git a/pyramid/tests/test_i18n.py b/pyramid/tests/test_i18n.py
index 588f0a7cc..97117a8cd 100644
--- a/pyramid/tests/test_i18n.py
+++ b/pyramid/tests/test_i18n.py
@@ -200,6 +200,19 @@ class Test_make_localizer(unittest.TestCase):
self.assertEqual(result.translate('Approve', 'deformsite'),
'Approve')
+ def test_territory_fallback(self):
+ import os
+ from pyramid.i18n import Localizer
+ here = os.path.dirname(__file__)
+ localedir = os.path.join(here, 'localeapp', 'locale')
+ localedirs = [localedir]
+ locale_name = 'de_DE'
+ result = self._callFUT(locale_name, localedirs)
+ self.assertEqual(result.__class__, Localizer)
+ self.assertEqual(result.translate('Approve', 'deformsite'),
+ 'Genehmigen')
+
+
class Test_get_localizer(unittest.TestCase):
def setUp(self):
cleanUp()
diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py
index 054c83d2b..6b2adbe09 100644
--- a/pyramid/tests/test_mako_templating.py
+++ b/pyramid/tests/test_mako_templating.py
@@ -354,6 +354,11 @@ class TestIntegration(unittest.TestCase):
self.assertRaises(TemplateLookupException, render,
'helloworld_not_here.mak', {})
+ def test_template_default_escaping(self):
+ from pyramid.renderers import render
+ result = render('nonminimal.mak', {'name':'<b>fred</b>'}).replace('\r','')
+ self.assertEqual(result, u'Hello, &lt;b&gt;fred&lt;/b&gt;!\n')
+
class TestPkgResourceTemplateLookup(unittest.TestCase):
def _makeOne(self, **kw):
from pyramid.mako_templating import PkgResourceTemplateLookup
diff --git a/pyramid/tests/test_urldispatch.py b/pyramid/tests/test_urldispatch.py
index 7df237aeb..ec45cb71e 100644
--- a/pyramid/tests/test_urldispatch.py
+++ b/pyramid/tests/test_urldispatch.py
@@ -366,6 +366,10 @@ class TestCompileRouteMatchFunctional(unittest.TestCase):
self.matches('*traverse', '/La%20Pe%C3%B1a/x',
{'traverse':(u'La Pe\xf1a', 'x')})
self.matches('/foo/:id.html', '/foo/bar.html', {'id':'bar'})
+ self.matches('/{num:[0-9]+}/*traverse', '/555/abc/def',
+ {'num':'555', 'traverse':('abc', 'def')})
+ self.matches('/{num:[0-9]*}/*traverse', '/555/abc/def',
+ {'num':'555', 'traverse':('abc', 'def')})
def test_generator_functional(self):
self.generates('', {}, '/')
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
index 43bc7e50f..230b18e54 100644
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -77,7 +77,7 @@ class RoutesMapper(object):
# stolen from bobo and modified
old_route_re = re.compile(r'(\:[a-zA-Z]\w*)')
-star_in_brackets = re.compile(r'\{[^\}]*\*\w*[^\}]*\}')
+star_at_end = re.compile(r'\*\w*$')
# The torturous nature of the regex named ``route_re`` below is due to the
# fact that we need to support at least one level of "inner" squigglies
@@ -97,9 +97,9 @@ def _compile_route(route):
route = '/' + route
star = None
-
- if '*' in route and not star_in_brackets.search(route):
+ if star_at_end.search(route):
route, star = route.rsplit('*', 1)
+
pat = route_re.split(route)
pat.reverse()
rpat = []