summaryrefslogtreecommitdiff
path: root/docs/tutorials/wiki
diff options
context:
space:
mode:
authorChristoph Zwerschke <cito@online.de>2016-04-19 20:07:12 +0200
committerChristoph Zwerschke <cito@online.de>2016-04-19 20:07:12 +0200
commit3629c49e46207ff5162a82883c14937e6ef4c186 (patch)
tree1306181202cb8313f16080789f5b9ab1eeb61d53 /docs/tutorials/wiki
parent804ba0b2f434781e77d2b5191f1cd76a490f6610 (diff)
parent6c16fb020027fac47e4d2e335cd9e264dba8aa3b (diff)
downloadpyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.gz
pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.bz2
pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.zip
Merge remote-tracking branch 'refs/remotes/Pylons/master'
Diffstat (limited to 'docs/tutorials/wiki')
-rw-r--r--docs/tutorials/wiki/NOTE-relocatable.txt13
-rw-r--r--docs/tutorials/wiki/authorization.rst523
-rw-r--r--docs/tutorials/wiki/background.rst9
-rw-r--r--docs/tutorials/wiki/basiclayout.rst180
-rw-r--r--docs/tutorials/wiki/definingmodels.rst56
-rw-r--r--docs/tutorials/wiki/definingviews.rst356
-rw-r--r--docs/tutorials/wiki/design.rst151
-rw-r--r--docs/tutorials/wiki/distributing.rst45
-rw-r--r--docs/tutorials/wiki/index.rst16
-rw-r--r--docs/tutorials/wiki/installation.rst447
-rw-r--r--docs/tutorials/wiki/src/authorization/CHANGES.txt3
-rw-r--r--docs/tutorials/wiki/src/authorization/README.txt8
-rw-r--r--docs/tutorials/wiki/src/authorization/development.ini67
-rw-r--r--docs/tutorials/wiki/src/authorization/production.ini61
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.cfg28
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.py44
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py40
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/login.py45
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/models.py8
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/security.py1
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/footerbg.pngbin333 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/headerbg.pngbin203 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/ie6.css8
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/middlebg.pngbin2797 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css65
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-small.pngbin7044 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.pngbin33055 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/transparent.gifbin49 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt124
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt122
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt130
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt125
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/tests.py125
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py104
-rw-r--r--docs/tutorials/wiki/src/basiclayout/README.txt8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/development.ini67
-rw-r--r--docs/tutorials/wiki/src/basiclayout/production.ini61
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.cfg27
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py45
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py24
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/models.py4
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/footerbg.pngbin333 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/headerbg.pngbin203 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/ie6.css8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/middlebg.pngbin2797 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css65
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-small.pngbin7044 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.pngbin33055 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/transparent.gifbin49 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt130
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/tests.py4
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views.py8
-rw-r--r--docs/tutorials/wiki/src/installation/CHANGES.txt4
-rw-r--r--docs/tutorials/wiki/src/installation/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/installation/README.txt12
-rw-r--r--docs/tutorials/wiki/src/installation/development.ini65
-rw-r--r--docs/tutorials/wiki/src/installation/production.ini60
-rw-r--r--docs/tutorials/wiki/src/installation/setup.py53
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/__init__.py18
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/models.py14
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt67
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/tests.py17
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views.py7
-rw-r--r--docs/tutorials/wiki/src/models/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/models/README.txt8
-rw-r--r--docs/tutorials/wiki/src/models/development.ini67
-rw-r--r--docs/tutorials/wiki/src/models/production.ini61
-rw-r--r--docs/tutorials/wiki/src/models/setup.cfg28
-rw-r--r--docs/tutorials/wiki/src/models/setup.py45
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py27
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/footerbg.pngbin333 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/headerbg.pngbin203 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/ie6.css8
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/middlebg.pngbin2797 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/pylons.css65
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/pyramid-small.pngbin7044 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/pyramid.pngbin33055 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/transparent.gifbin49 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt130
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/tests.py46
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views.py6
-rw-r--r--docs/tutorials/wiki/src/tests/CHANGES.txt4
-rw-r--r--docs/tutorials/wiki/src/tests/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/tests/README.txt12
-rw-r--r--docs/tutorials/wiki/src/tests/development.ini65
-rw-r--r--docs/tutorials/wiki/src/tests/production.ini60
-rw-r--r--docs/tutorials/wiki/src/tests/setup.py54
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py27
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/models.py29
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/security.py7
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt72
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/login.pt74
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt67
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/view.pt72
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/tests.py65
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views.py116
-rw-r--r--docs/tutorials/wiki/src/views/CHANGES.txt5
-rw-r--r--docs/tutorials/wiki/src/views/README.txt8
-rw-r--r--docs/tutorials/wiki/src/views/development.ini67
-rw-r--r--docs/tutorials/wiki/src/views/production.ini61
-rw-r--r--docs/tutorials/wiki/src/views/setup.cfg28
-rw-r--r--docs/tutorials/wiki/src/views/setup.py43
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py26
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/footerbg.pngbin333 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/headerbg.pngbin203 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/ie6.css8
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/middlebg.pngbin2797 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/pylons.css65
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/pyramid-small.pngbin7044 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/pyramid.pngbin33055 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/transparent.gifbin49 -> 0 bytes
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/edit.pt115
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt130
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/view.pt120
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/tests.py128
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views.py42
-rw-r--r--docs/tutorials/wiki/tests.rst73
137 files changed, 4238 insertions, 2502 deletions
diff --git a/docs/tutorials/wiki/NOTE-relocatable.txt b/docs/tutorials/wiki/NOTE-relocatable.txt
new file mode 100644
index 000000000..e942caba8
--- /dev/null
+++ b/docs/tutorials/wiki/NOTE-relocatable.txt
@@ -0,0 +1,13 @@
+We specifically use relative package references where possible so this demo
+works even if the user names their package (in the '$VENV/bin/pcreate -s
+zodb ...' step) something other than 'tutorial'.
+
+Specifically:
+
+- use relative imports
+- use plain relative URLs for resources (like stylesheets and images) in
+ page templates.
+
+Direct uses of the package name, like in __init__.py 'config.scan()'
+statements, are already adjusted by the paster/pcreate, so we don't have to
+worry about them.
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 358c1d5eb..44097b35b 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -1,297 +1,372 @@
+.. _wiki_adding_authorization:
+
====================
-Adding Authorization
+Adding authorization
====================
-Our application currently allows anyone with access to the server to view,
-edit, and add pages to our wiki. For purposes of demonstration we'll change
-our application to allow people whom are members of a *group* named
-``group:editors`` 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 :term:`authorization` and :term:`authentication`. We'll make
-use of both features to provide security to our application.
+:app:`Pyramid` provides facilities for :term:`authentication` and
+:term:`authorization`. We'll make use of both features to provide security to
+our application. Our application currently allows anyone with access to the
+server to view, edit, and add pages to our wiki. We'll change that to allow
+only people who are members of a *group* named ``group:editors`` to add and
+edit wiki pages, but we'll continue allowing anyone with access to the server
+to view pages.
-We will add an :term:`authentication policy` and an
-:term:`authorization policy` to our :term:`application
-registry`, add a ``security.py`` module and give our :term:`root`
-resource an :term:`ACL`.
+We will also add a login page and a logout link on all the pages. The login
+page will be shown when a user is denied access to any of the views that
+require permission, instead of a default "403 Forbidden" page.
-Then we will add ``login`` and ``logout`` views, and modify the
-existing views to make them return a ``logged_in`` flag to the
-renderer and add :term:`permission` declarations to their ``view_config``
-decorators.
+We will implement the access control with the following steps:
-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.
+* Add users and groups (``security.py``, a new module).
+* Add an :term:`ACL` (``models.py``).
+* Add an :term:`authentication policy` and an :term:`authorization policy`
+ (``__init__.py``).
+* Add :term:`permission` declarations to the ``edit_page`` and ``add_page``
+ views (``views.py``).
-The source code for this tutorial stage can be browsed via
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/>`_.
+Then we will add the login and logout feature:
-Adding Authentication and Authorization Policies
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+* Add ``login`` and ``logout`` views (``views.py``).
+* Add a login template (``login.pt``).
+* Make the existing views return a ``logged_in`` flag to the renderer
+ (``views.py``).
+* Add a "Logout" link to be shown when logged in and viewing or editing a page
+ (``view.pt``, ``edit.pt``).
-We'll change our package's ``__init__.py`` file to enable an
-``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable
-declarative security checking. We need to import the new policies:
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 4-5,8
+Access control
+--------------
+
+Add users and groups
+~~~~~~~~~~~~~~~~~~~~
+
+Create a new ``tutorial/security.py`` module with the
+following content:
+
+.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
:language: python
-Then, we'll add those policies to the configuration:
+The ``groupfinder`` function accepts a userid and a request and
+returns one of these values:
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 16-18,26-28
+- If the userid exists in the system, it 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, it will return ``None``.
+
+For example, ``groupfinder('editor', request )`` returns ``['group:editor']``,
+``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin',
+request)`` returns ``None``. We will use ``groupfinder()`` as an
+:term:`authentication policy` "callback" that will provide the
+:term:`principal` or principals for a user.
+
+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.
+
+Add an ACL
+~~~~~~~~~~
+
+Open ``tutorial/models.py`` and add the following import
+statement at the head:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :lines: 4-7
:linenos:
:language: python
-Note that the creation of an ``AuthTktAuthenticationPolicy`` requires two
-arguments: ``secret`` and ``callback``. ``secret`` is a string representing
-an encryption key used by the "authentication ticket" machinery represented
-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:
+Add the following lines to the ``Wiki`` class:
-.. literalinclude:: src/authorization/tutorial/__init__.py
+.. literalinclude:: src/authorization/tutorial/models.py
+ :lines: 9-13
:linenos:
+ :lineno-start: 9
+ :emphasize-lines: 4-5
:language: python
-Adding ``security.py``
-~~~~~~~~~~~~~~~~~~~~~~
+We import :data:`~pyramid.security.Allow`, an action that means that
+permission is allowed, and :data:`~pyramid.security.Everyone`, a special
+:term:`principal` that is associated to all requests. Both are used in the
+:term:`ACE` entries that make up the ACL.
-Add a ``security.py`` module within your package (in the same
-directory as ``__init__.py``, ``views.py``, etc.) with the following
-content:
+The ACL is a list that needs to be named `__acl__` and be an attribute of a
+class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry
+allows any user the `view` permission. The second entry allows the
+``group:editors`` principal the `edit` permission.
-.. literalinclude:: src/authorization/tutorial/security.py
+The ``Wiki`` class that contains the ACL is the :term:`resource` constructor
+for the :term:`root` resource, which is a ``Wiki`` instance. The ACL is
+provided to each view in the :term:`context` of the request as the ``context``
+attribute.
+
+It's only happenstance that we're assigning this ACL at class scope. An ACL
+can be attached to an object *instance* too; this is how "row level security"
+can be achieved in :app:`Pyramid` applications. We actually need only *one*
+ACL for the entire system, however, because our security requirements are
+simple, so this feature is not demonstrated. See :ref:`assigning_acls` for
+more information about what an :term:`ACL` represents.
+
+Add authentication and authorization policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Open ``tutorial/__init__.py`` and add the highlighted import
+statements:
+
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 1-8
:linenos:
+ :emphasize-lines: 4-5,8
:language: python
-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 of any groups). If the userid *does not* exist in the system,
-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
-structure).
+Now add those policies to the configuration:
-Giving Our Root Resource an ACL
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :lines: 18-23
+ :linenos:
+ :lineno-start: 18
+ :emphasize-lines: 1-3,5-6
+ :language: python
-We need to give our root resource object an :term:`ACL`. This ACL will be
-sufficient to provide enough information to the :app:`Pyramid` security
-machinery to challenge a user who doesn't have appropriate credentials when
-he attempts to invoke the ``add_page`` or ``edit_page`` views.
+Only the highlighted lines need to be added.
-We need to perform some imports at module scope in our ``models.py`` file:
+We are enabling an ``AuthTktAuthenticationPolicy``, which is based in an auth
+ticket that may be included in the request. We are also enabling an
+``ACLAuthorizationPolicy``, which uses an ACL to determine the *allow* or
+*deny* outcome for a view.
-.. code-block:: python
- :linenos:
+Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
+constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is
+a string representing an encryption key used by the "authentication ticket"
+machinery represented by this policy: it is required. The ``callback`` is the
+``groupfinder()`` function that we created before.
- from pyramid.security import Allow
- from pyramid.security import Everyone
+Add permission declarations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter
+to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
-Our root resource object is a ``Wiki`` instance. We'll add the following
-line at class scope to our ``Wiki`` class:
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 50-52
+ :emphasize-lines: 2-3
+ :language: python
-.. code-block:: python
- :linenos:
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 70-72
+ :emphasize-lines: 2-3
+ :language: python
- __acl__ = [ (Allow, Everyone, 'view'),
- (Allow, 'group:editors', 'edit') ]
+Only the highlighted lines, along with their preceding commas, need to be
+edited and added.
-It's only happenstance that we're assigning this ACL at class scope. An ACL
-can be attached to an object *instance* too; this is how "row level security"
-can be achieved in :app:`Pyramid` applications. We actually only need *one*
-ACL for the entire system, however, because our security requirements are
-simple, so this feature is not demonstrated.
+The result is that only users who possess the ``edit`` permission at the time
+of the request may invoke those two views.
-Our resulting ``models.py`` file will now look like so:
+Add a ``permission='view'`` parameter to the ``@view_config`` decorator for
+``view_wiki()`` and ``view_page()`` as follows:
-.. literalinclude:: src/authorization/tutorial/models.py
- :linenos:
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 23-24
+ :emphasize-lines: 1-2
:language: python
-Adding Login and Logout Views
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 28-29
+ :emphasize-lines: 1-2
+ :language: python
+
+Only the highlighted lines, along with their preceding commas, need to be
+edited and added.
-We'll add a ``login`` view which renders a login form and processes
-the post from the login form, checking credentials.
+This allows anyone to invoke these two views.
-We'll also add a ``logout`` view to our application and provide a link
-to it. This view will clear the credentials of the logged in user and
+We are done with the changes needed to control access. The changes that
+follow will add the login and logout feature.
+
+Login, logout
+-------------
+
+Add login and logout views
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We'll add a ``login`` view which renders a login form and processes the post
+from the login form, checking credentials.
+
+We'll also add a ``logout`` view callable to our application and provide a
+link to it. This view will clear the credentials of the logged in user and
redirect back to the front page.
-We'll add a different file (for presentation convenience) to add login
-and logout views. Add a file named ``login.py`` to your application
-(in the same directory as ``views.py``) with the following content:
+Add the following import statements to the head of
+``tutorial/views.py``:
-.. literalinclude:: src/authorization/tutorial/login.py
- :linenos:
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 6-17
+ :emphasize-lines: 1-12
:language: python
-Note that the ``login`` view callable in the ``login.py`` file has *two* view
-configuration decorators. The order of these decorators is unimportant.
-Each just adds a different :term:`view configuration` for the ``login`` view
-callable.
-
-The first view configuration decorator configures the ``login`` view callable
-so it will be invoked when someone visits ``/login`` (when the context is a
-Wiki and the view name is ``login``). The second decorator (with context of
-``pyramid.exceptions.Forbidden``) specifies a :term:`forbidden view`. This
-configures our login view to be presented to the user when :app:`Pyramid`
-detects that a view invocation can not be authorized. Because we've
-configured a forbidden view, the ``login`` view callable will be invoked
-whenever one of our users tries to execute a view callable that they are not
-allowed to invoke as determined by the :term:`authorization policy` in use.
-In our application, for example, this means that if a user has not logged in,
-and he tries to add or edit a Wiki page, he will be shown the login form.
-Before being allowed to continue on to the add or edit form, he will have to
-provide credentials that give him permission to add or edit via this login
-form.
-
-Changing Existing Views
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Then we need to change each of our ``view_page``, ``edit_page`` and
-``add_page`` views in ``views.py`` to pass a "logged in" parameter
-into its template. We'll add something like this to each view body:
-
-.. ignore-next-block
-.. code-block:: python
- :linenos:
+All the highlighted lines need to be added or edited.
- from pyramid.security import authenticated_userid
- logged_in = authenticated_userid(request)
+:meth:`~pyramid.view.forbidden_view_config` will be used to customize the
+default 403 Forbidden page. :meth:`~pyramid.security.remember` and
+:meth:`~pyramid.security.forget` help to create and expire an auth ticket
+cookie.
-We'll then change the return value of each view that has an associated
-``renderer`` to pass the resulting ``logged_in`` value to the
-template. For example:
+Now add the ``login`` and ``logout`` views at the end of the file:
-.. ignore-next-block
-.. code-block:: python
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 82-116
:linenos:
+ :lineno-start: 82
+ :language: python
+
+``login()`` has two decorators:
+
+- a ``@view_config`` decorator which associates it with the ``login`` route
+ and makes it visible when we visit ``/login``,
+- a ``@forbidden_view_config`` decorator which turns it into a
+ :term:`forbidden view`. ``login()`` will be invoked when a user tries to
+ execute a view callable for which they lack authorization. For example, if
+ a user has not logged in and tries to add or edit a Wiki page, they will be
+ shown the login form before being allowed to continue.
- return dict(page = context,
- content = content,
- logged_in = logged_in,
- edit_url = edit_url)
-
-Adding ``permission`` Declarations to our ``view_config`` Decorators
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To protect each of our views with a particular permission, we need to pass a
-``permission`` argument to each of our :class:`pyramid.view.view_config`
-decorators. To do so, within ``views.py``:
-
-- We add ``permission='view'`` to the decorator attached to the
- ``view_wiki`` and ``view_page`` view functions. This makes the
- assertion that only users who possess the ``view`` permission
- against the context resource at the time of the request may
- invoke these views. We've granted
- :data:`pyramid.security.Everyone` the view permission at the
- root model via its ACL, so everyone will be able to invoke the
- ``view_wiki`` and ``view_page`` views.
-
-- We add ``permission='edit'`` to the decorator attached to the
- ``add_page`` and ``edit_page`` view functions. This makes the
- assertion that only users who possess the effective ``edit``
- permission against the context resource at the time of the
- request may invoke these views. We've granted the
- ``group:editors`` principal the ``edit`` permission at the
- root model via its ACL, so only a user whom is a member of
- the group named ``group:editors`` will able to invoke the
- ``add_page`` or ``edit_page`` views. We've likewise given
- the ``editor`` user membership to this group via the
- ``security.py`` file by mapping him to the ``group:editors``
- group in the ``GROUPS`` data structure (``GROUPS
- = {'editor':['group:editors']}``); the ``groupfinder``
- function consults the ``GROUPS`` data structure. This means
- that the ``editor`` user can add and edit pages.
-
-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``.
+The order of these two :term:`view configuration` decorators is unimportant.
+
+``logout()`` is decorated with a ``@view_config`` decorator which associates
+it with the ``logout`` route. It will be invoked when we visit ``/logout``.
+
+Add the ``login.pt`` Template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Create ``tutorial/templates/login.pt`` with the following content:
.. literalinclude:: src/authorization/tutorial/templates/login.pt
- :language: xml
+ :language: html
+
+The above template is referenced in the login view that we just added in
+``views.py``.
+
+Return a ``logged_in`` flag to the renderer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Change ``view.pt`` and ``edit.pt``
+Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to
+the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as
+follows:
+
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 47-48
+ :emphasize-lines: 1-2
+ :language: python
+
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 67-68
+ :emphasize-lines: 1-2
+ :language: python
+
+.. literalinclude:: src/authorization/tutorial/views.py
+ :lines: 78-80
+ :emphasize-lines: 2-3
+ :language: python
+
+Only the highlighted lines need to be added or edited.
+
+The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if
+the user is not authenticated, or a userid if the user is authenticated.
+
+Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-We'll also need to change our ``edit.pt`` and ``view.pt`` templates to
-display a "Logout" link if someone is logged in. This link will
-invoke the logout view.
+Open ``tutorial/templates/edit.pt`` and
+``tutorial/templates/view.pt`` and add the following code as
+indicated by the highlighted lines.
-To do so we'll add this to both templates within the ``<div id="right"
-class="app-welcome align-right">`` div:
+.. literalinclude:: src/authorization/tutorial/templates/edit.pt
+ :lines: 34-38
+ :emphasize-lines: 3-5
+ :language: html
-.. code-block:: xml
+The attribute ``tal:condition="logged_in"`` will make the element be included
+when ``logged_in`` is any user id. The link will invoke the logout view. The
+above element will not be included if ``logged_in`` is ``None``, such as when
+a user is not authenticated.
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
+Reviewing our changes
+---------------------
-Seeing Our Changes To ``views.py`` and our Templates
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Our ``tutorial/__init__.py`` will look like this when we're done:
-Our ``views.py`` module will look something like this when we're done:
+.. literalinclude:: src/authorization/tutorial/__init__.py
+ :linenos:
+ :emphasize-lines: 4-5,8,18-20,22-23
+ :language: python
+
+Only the highlighted lines need to be added or edited.
+
+Our ``tutorial/models.py`` will look like this when we're done:
+
+.. literalinclude:: src/authorization/tutorial/models.py
+ :linenos:
+ :emphasize-lines: 4-7,12-13
+ :language: python
+
+Only the highlighted lines need to be added or edited.
+
+Our ``tutorial/views.py`` will look like this when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
+ :emphasize-lines: 8,11-15,17,24,29,48,52,68,72,80,82-120
:language: python
-Our ``edit.pt`` template will look something like this when we're done:
+Only the highlighted lines need to be added or edited.
+
+Our ``tutorial/templates/edit.pt`` template will look like this when
+we're done:
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
:linenos:
- :language: xml
+ :emphasize-lines: 36-38
+ :language: html
+
+Only the highlighted lines need to be added or edited.
-Our ``view.pt`` template will look something like this when we're done:
+Our ``tutorial/templates/view.pt`` template will look like this when
+we're done:
.. literalinclude:: src/authorization/tutorial/templates/view.pt
:linenos:
- :language: xml
-
-Viewing the Application in a Browser
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We can finally examine our application in a browser. The views we'll try are
-as follows:
-
-- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki``
- view. This always redirects to the ``view_page`` view of the ``FrontPage``
- page resource. It is executable by any user.
-
-- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes the
- ``view_page`` view of the ``FrontPage`` Page resource. This is because
- it's the :term:`default view` (a view without a ``name``) for ``Page``
- resources. It is executable by any user.
-
-- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser invokes
- the edit view for the ``FrontPage`` Page resource. It is executable by
- only the ``editor`` user. If a different user (or the anonymous user)
- invokes it, a login form will be displayed. Supplying the credentials with
- the username ``editor``, password ``editor`` will show the edit page form
- being displayed.
-
-- Visiting ``http://localhost:6543/add_page/SomePageName`` in a
- browser invokes the add view for a page. It is executable by only
- the ``editor`` user. If a different user (or the anonymous user)
- invokes it, a login form will be displayed. Supplying the
- credentials with the username ``editor``, password ``editor`` will
- show the edit page form being displayed.
-
-- 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.
+ :emphasize-lines: 36-38
+ :language: html
+
+Only the highlighted lines need to be added or edited.
+
+Viewing the application in a browser
+------------------------------------
+
+We can finally examine our application in a browser (See
+:ref:`wiki-start-the-application`). Launch a browser and visit each of the
+following URLs, checking that the result is as expected:
+
+- http://localhost:6543/ invokes the ``view_wiki`` view. This always
+ redirects to the ``view_page`` view of the ``FrontPage`` Page resource. It
+ is executable by any user.
+
+- http://localhost:6543/FrontPage invokes the ``view_page`` view of the
+ ``FrontPage`` Page resource. This is because it's the :term:`default view`
+ (a view without a ``name``) for ``Page`` resources. It is executable by any
+ user.
+
+- http://localhost:6543/FrontPage/edit_page invokes the edit view for the
+ FrontPage object. It is executable by only the ``editor`` user. If a
+ different user (or the anonymous user) invokes it, a login form will be
+ displayed. Supplying the credentials with the username ``editor``, password
+ ``editor`` will display the edit page form.
+
+- http://localhost:6543/add_page/SomePageName invokes the add view for a page.
+ It is executable by only the ``editor`` user. If a different user (or the
+ anonymous user) invokes it, a login form will be displayed. Supplying the
+ credentials with the username ``editor``, password ``editor`` will display
+ the edit page form.
+
+- 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/background.rst b/docs/tutorials/wiki/background.rst
index e49407b70..31dcd6b53 100644
--- a/docs/tutorials/wiki/background.rst
+++ b/docs/tutorials/wiki/background.rst
@@ -1,3 +1,5 @@
+.. _wiki_background:
+
==========
Background
==========
@@ -11,8 +13,11 @@ Python web framework experience.
To code along with this tutorial, the developer will need a UNIX
machine with development tools (Mac OS X with XCode, any Linux or BSD
-variant, etc) *or* he will need a Windows system of any kind.
+variant, etc.) *or* a Windows system of any kind.
+
+.. warning::
-This tutorial targets :app:`Pyramid` version 1.0.
+ This tutorial has been written for Python 2. It is unlikely to work
+ without modification under Python 3.
Have fun!
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index 66cf37e4e..20bfdf754 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -1,76 +1,76 @@
+.. _wiki_basic_layout:
+
============
Basic Layout
============
-The starter files generated by the ``pyramid_zodb`` scaffold are basic, but
+The starter files generated by the ``zodb`` scaffold are very basic, but
they provide a good orientation for the high-level patterns common to most
-:term:`traversal` -based :app:`Pyramid` (and :term:`ZODB` based) projects.
+:term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects.
-The source code for this tutorial stage can be browsed via
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/>`_.
-App Startup with ``__init__.py``
---------------------------------
+Application configuration with ``__init__.py``
+----------------------------------------------
A directory on disk can be turned into a Python :term:`package` by containing
an ``__init__.py`` file. Even if empty, this marks a directory as a Python
-package. Our application uses ``__init__.py`` as both a package marker, as
-well as to contain application configuration code.
+package. We use ``__init__.py`` both as a marker, indicating the directory in
+which it's contained is a package, and to contain application configuration
+code.
+
+When you run the application using the ``pserve`` command using the
+``development.ini`` generated configuration file, the application
+configuration points at a setuptools *entry point* described as
+``egg:tutorial``. In our application, because the application's ``setup.py``
+file says so, this entry point happens to be the ``main`` function within the
+file named ``__init__.py``.
-When you run the application using the ``paster`` command using the
-``development.ini`` generated config file, the application configuration
-points at a Setuptools *entry point* described as ``egg:tutorial``. In our
-application, because the application's ``setup.py`` file says so, this entry
-point happens to be the ``main`` function within the file named
-``__init__.py``:
+Open ``tutorial/__init__.py``. It should already contain the following:
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :linenos:
+ :language: py
#. *Lines 1-3*. Perform some dependency imports.
-#. *Line 8*. Get the ZODB configuration from the ``development.ini``
- file's ``[app:main]`` section represented by the ``settings``
- dictionary passed to our ``app`` function. This will be a URI
- (something like ``file:///path/to/Data.fs``).
+#. *Lines 6-8*. Define a :term:`root factory` for our Pyramid application.
-#. *Line 12*. We create a "finder" object using the
- ``PersistentApplicationFinder`` helper class, passing it the ZODB
- URI and the "appmaker" we've imported from ``models.py``.
+#. *Line 11*. ``__init__.py`` defines a function named ``main``.
-#. *Lines 13 - 14*. We create a :term:`root factory` which uses the
- finder to return a ZODB root object.
+#. *Line 14*. We construct a :term:`Configurator` with a root
+ factory and the settings keywords parsed by :term:`PasteDeploy`. The root
+ factory is named ``root_factory``.
-#. *Line 15*. We construct a :term:`Configurator` with a :term:`root
- factory` and the settings keywords parsed by PasteDeploy. The root
- factory is named ``get_root``.
+#. *Line 15*. Include support for the :term:`Chameleon` template rendering
+ bindings, allowing us to use the ``.pt`` templates.
-#. *Line 16*. Register a 'static view' which answers requests which start
- with with URL path ``/static`` using the
- :meth:`pyramid.config.Configurator.add_static_view method`. This
+#. *Line 16*. Register a "static view", which answers requests whose URL
+ paths start with ``/static``, using the
+ :meth:`pyramid.config.Configurator.add_static_view` method. This
statement registers a view that will serve up static assets, such as CSS
and image files, for us, in this case, at
``http://localhost:6543/static/`` and below. The first argument is the
"name" ``static``, which indicates that the URL path prefix of the view
- will be ``/static``. the The second argument of this tag is the "path",
- which is an :term:`asset specification`, so it finds the resources it
- should serve within the ``static`` directory inside the ``tutorial``
- package.
+ will be ``/static``. The second argument of this tag is the "path",
+ which is a relative :term:`asset specification`, so it finds the resources
+ it should serve within the ``static`` directory inside the ``tutorial``
+ package. Alternatively the scaffold could have used an *absolute* asset
+ specification as the path (``tutorial:static``).
#. *Line 17*. Perform a :term:`scan`. A scan will find :term:`configuration
- decoration`, such as view configuration decorators
- (e.g. ``@view_config``) in the source code of the ``tutorial`` package and
- will take actions based on these decorators. The argument to
- :meth:`~pyramid.config.Configurator.scan` is the package name to scan,
- which is ``tutorial``.
+ decoration`, such as view configuration decorators (e.g., ``@view_config``)
+ in the source code of the ``tutorial`` package and will take actions based
+ on these decorators. We don't pass any arguments to
+ :meth:`~pyramid.config.Configurator.scan`, which implies that the scan
+ should take place in the current package (in this case, ``tutorial``).
+ The scaffold could have equivalently said ``config.scan('tutorial')``, but
+ it chose to omit the package name argument.
#. *Line 18*. Use the
:meth:`pyramid.config.Configurator.make_wsgi_app` method
to return a :term:`WSGI` application.
-Resources and Models with ``models.py``
+Resources and models with ``models.py``
---------------------------------------
:app:`Pyramid` uses the word :term:`resource` to describe objects arranged
@@ -79,33 +79,33 @@ hierarchically in a :term:`resource tree`. This tree is consulted by
tree represents the site structure, but it *also* represents the
:term:`domain model` of the application, because each resource is a node
stored persistently in a :term:`ZODB` database. The ``models.py`` file is
-where the ``pyramid_zodb`` scaffold put the classes that implement our
-resource objects, each of which happens also to be a domain model object.
+where the ``zodb`` scaffold put the classes that implement our
+resource objects, each of which also happens to be a domain model object.
Here is the source for ``models.py``:
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/models.py
+ :linenos:
+ :language: python
-#. *Lines 3-4*. The ``MyModel`` :term:`resource` class is implemented here.
- Instances of this class will be capable of being persisted in :term:`ZODB`
+#. *Lines 4-5*. The ``MyModel`` :term:`resource` class is implemented here.
+ Instances of this class are capable of being persisted in :term:`ZODB`
because the class inherits from the
:class:`persistent.mapping.PersistentMapping` class. The ``__parent__``
and ``__name__`` are important parts of the :term:`traversal` protocol.
By default, have these as ``None`` indicating that this is the
:term:`root` object.
-#. *Lines 6-12*. ``appmaker`` is used to return the *application
+#. *Lines 8-14*. ``appmaker`` is used to return the *application
root* object. It is called on *every request* to the
:app:`Pyramid` application. It also performs bootstrapping by
*creating* an application root (inside the ZODB root object) if one
- does not already exist.
+ does not already exist. It is used by the ``root_factory`` we've defined
+ in our ``__init__.py``.
- We do so by first seeing if the database has the persistent
- application root. If not, we make an instance, store it, and
- commit the transaction. We then return the application root
- object.
+ Bootstrapping is done by first seeing if the database has the persistent
+ application root. If not, we make an instance, store it, and commit the
+ transaction. We then return the application root object.
Views With ``views.py``
-----------------------
@@ -116,19 +116,19 @@ the URL ``http://localhost:6543/``.
Here is the source for ``views.py``:
- .. literalinclude:: src/basiclayout/tutorial/views.py
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/views.py
+ :linenos:
+ :language: python
Let's try to understand the components in this module:
#. *Lines 1-2*. Perform some dependency imports.
-#. *Line 4*. Use the :func:`pyramid.view.view_config` :term:`configuration
+#. *Line 5*. Use the :func:`pyramid.view.view_config` :term:`configuration
decoration` to perform a :term:`view configuration` registration. This
view configuration registration will be activated when the application is
started. It will be activated by virtue of it being found as the result
- of a :term:`scan` (when Line 17 of ``__init__.py`` is run).
+ of a :term:`scan` (when Line 14 of ``__init__.py`` is run).
The ``@view_config`` decorator accepts a number of keyword arguments. We
use two keyword arguments here: ``context`` and ``renderer``.
@@ -140,20 +140,23 @@ Let's try to understand the components in this module:
model, this view callable will be invoked.
The ``renderer`` argument names an :term:`asset specification` of
- ``tutorial:templates/mytemplate.pt``. This asset specification points at
- a :term:`Chameleon` template which lives in the ``mytemplate.pt`` file
+ ``templates/mytemplate.pt``. This asset specification points at a
+ :term:`Chameleon` template which lives in the ``mytemplate.pt`` file
within the ``templates`` directory of the ``tutorial`` package. And
indeed if you look in the ``templates`` directory of this package, you'll
see a ``mytemplate.pt`` template file, which renders the default home page
- of the generated project.
+ of the generated project. This asset specification is *relative* (to the
+ view.py's current package). Alternatively we could have used the
+ absolute asset specification ``tutorial:templates/mytemplate.pt``, but
+ chose to use the relative version.
Since this call to ``@view_config`` doesn't pass a ``name`` argument, the
``my_view`` function which it decorates represents the "default" view
callable used when the context is of the type ``MyModel``.
-#. *Lines 5-6*. We define a :term:`view callable` named ``my_view``, which
+#. *Lines 6-7*. We define a :term:`view callable` named ``my_view``, which
we decorated in the step above. This view callable is a *function* we
- write generated by the ``pyramid_zodb`` scaffold that is given a
+ write generated by the ``zodb`` scaffold that is given a
``request`` and which returns a dictionary. The ``mytemplate.pt``
:term:`renderer` named by the asset specification in the step above will
convert this dictionary to a :term:`response` on our behalf.
@@ -162,45 +165,18 @@ Let's try to understand the components in this module:
dictionary is used by the template named by the ``mytemplate.pt`` asset
specification to fill in certain values on the page.
-The WSGI Pipeline in ``development.ini``
-----------------------------------------
+Configuration in ``development.ini``
+------------------------------------
The ``development.ini`` (in the tutorial :term:`project` directory, as
opposed to the tutorial :term:`package` directory) looks like this:
-.. literalinclude:: src/views/development.ini
- :language: ini
-
-
-Note the existence of a ``[pipeline:main]`` section which specifies our WSGI
-pipeline. This "pipeline" will be served up as our WSGI application. As far
-as the WSGI server is concerned the pipeline *is* our application. Simpler
-configurations don't use a pipeline: instead they expose a single WSGI
-application as "main". Our setup is more complicated, so we use a pipeline
-composed of :term:`middleware`.
-
-The ``egg:WebError#evalerror`` middleware is at the "top" of the pipeline.
-This is middleware which displays debuggable errors in the browser while
-you're developing (not recommended for a production system).
-
-The ``egg:repoze.zodbconn#closer`` middleware is in the middle of the
-pipeline. This is a piece of middleware which closes the ZODB connection
-opened by the ``PersistentApplicationFinder`` at the end of the request.
-
-The ``egg:repoze.retry#retry`` middleware catches ``ConflictError``
-exceptions from ZODB and retries the request up to three times (ZODB is an
-optimistic concurrency database that relies on application-level transaction
-retries when a conflict occurs).
-
-The ``tm`` middleware is the last piece of middleware in the pipeline. This
-commits a transaction near the end of the request unless there's an exception
-raised or the HTTP response code is an error code. The ``tm`` refers to the
-``[filter:tm]`` section beneath the pipeline declaration, which configures
-the transaction manager.
+.. literalinclude:: src/basiclayout/development.ini
+ :language: ini
-The final line in the ``[pipeline:main]`` section is ``tutorial``, which
-refers to the ``[app:tutorial]`` section above it. The ``[app:tutorial]``
-section is the section which actually defines our application settings. The
-values within this section are passed as ``**settings`` to the ``main``
+Note the existence of a ``[app:main]`` section which specifies our WSGI
+application. Our ZODB database settings are specified as the
+``zodbconn.uri`` setting within this section. This value, and the other
+values within this section, are passed as ``**settings`` to the ``main``
function we defined in ``__init__.py`` when the server is started via
-``paster serve``.
+``pserve``.
diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst
index baf497458..73dce14d5 100644
--- a/docs/tutorials/wiki/definingmodels.rst
+++ b/docs/tutorials/wiki/definingmodels.rst
@@ -1,9 +1,11 @@
+.. _wiki_defining_the_domain_model:
+
=========================
Defining the Domain Model
=========================
-The first change we'll make to our stock paster-generated application will be
-to define two :term:`resource` constructors, one representing a wiki page,
+The first change we'll make to our stock ``pcreate``-generated application will
+be to define two :term:`resource` constructors, one representing a wiki page,
and another representing the wiki as a mapping of wiki page names to page
objects. We'll do this inside our ``models.py`` file.
@@ -14,12 +16,9 @@ constructors". Both our Page and Wiki constructors will be class objects. A
single instance of the "Wiki" class will serve as a container for "Page"
objects, which will be instances of the "Page" class.
-The source code for this tutorial stage can be browsed via
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/models/
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/models/>`_.
-Deleting the Database
----------------------
+Delete the database
+-------------------
In the next step, we're going to remove the ``MyModel`` Python model
class from our ``models.py`` file. Since this class is referred to within
@@ -30,17 +29,23 @@ directory before proceeding any further. It's always fine to do this as long
as you don't care about the content of the database; the database itself will
be recreated as necessary.
-Making Edits to ``models.py``
------------------------------
+Edit ``models.py``
+------------------
.. note::
- There is nothing automagically special about the filename ``models.py``. A
- project may have many models throughout its codebase in arbitrarily-named
- files. Files implementing models often have ``model`` in their filenames,
+ There is nothing special about the filename ``models.py``. A
+ project may have many models throughout its codebase in arbitrarily named
+ files. Files implementing models often have ``model`` in their filenames
or they may live in a Python subpackage of your application package named
``models``, but this is only by convention.
+Open ``tutorial/models.py`` file and edit it to look like the following:
+
+.. literalinclude:: src/models/tutorial/models.py
+ :linenos:
+ :language: python
+
The first thing we want to do is remove the ``MyModel`` class from the
generated ``models.py`` file. The ``MyModel`` class is only a sample and
we're not going to use it.
@@ -59,11 +64,11 @@ of the root model is also always ``None``.
Then we'll add a ``Page`` class. This class should inherit from the
:class:`persistent.Persistent` class. We'll also give it an ``__init__``
method that accepts a single parameter named ``data``. This parameter will
-contain the :term:`ReStructuredText` body representing the wiki page content.
+contain the :term:`reStructuredText` body representing the wiki page content.
Note that ``Page`` objects don't have an initial ``__name__`` or
``__parent__`` attribute. All objects in a traversal graph must have a
``__name__`` and a ``__parent__`` attribute. We don't specify these here
-because both ``__name__`` and ``__parent__`` will be set by by a :term:`view`
+because both ``__name__`` and ``__parent__`` will be set by a :term:`view`
function when a Page is added to our Wiki mapping.
As a last step, we want to change the ``appmaker`` function in our
@@ -73,28 +78,13 @@ front page) into the Wiki within the ``appmaker``. This will provide
:term:`traversal` a :term:`resource tree` to work against when it attempts to
resolve URLs to resources.
-We're using a mini-framework callable named ``PersistentApplicationFinder``
-in our application (see ``__init__.py``). A ``PersistentApplicationFinder``
-accepts a ZODB URL as well as an "appmaker" callback. This callback
-typically lives in the ``models.py`` file. We'll just change this function,
-making the necessary edits.
-
-Looking at the Result of Our Edits to ``models.py``
----------------------------------------------------
-
-The result of all of our edits to ``models.py`` will end up looking
-something like this:
-
-.. literalinclude:: src/models/tutorial/models.py
- :linenos:
- :language: python
-
-Viewing the Application in a Browser
-------------------------------------
+View the application in a browser
+---------------------------------
We can't. At this point, our system is in a "non-runnable" state; we'll need
to change view-related files in the next chapter to be able to start the
-application successfully. If you try to start the application, you'll wind
+application successfully. If you try to start the application (See
+:ref:`wiki-start-the-application`), you'll wind
up with a Python traceback on your console that ends with this exception:
.. code-block:: text
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index ae4fa6ffb..ac94d8059 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -1,3 +1,5 @@
+.. _wiki_defining_views:
+
==============
Defining Views
==============
@@ -7,7 +9,9 @@ application is typically a simple Python function that accepts two
parameters: :term:`context` and :term:`request`. A view callable is
assumed to return a :term:`response` object.
-.. note:: A :app:`Pyramid` view can also be defined as callable
+.. note::
+
+ A :app:`Pyramid` view can also be defined as callable
which accepts *only* a :term:`request` argument. You'll see
this one-argument pattern used in other :app:`Pyramid` tutorials
and applications. Either calling convention will work in any
@@ -15,7 +19,7 @@ assumed to return a :term:`response` object.
interchangeably as necessary. In :term:`traversal` based applications,
URLs are mapped to a context :term:`resource`, and since our
:term:`resource tree` also represents our application's
- "domain model", we're often interested in the context, because
+ "domain model", we're often interested in the context because
it represents the persistent storage of our application. For
this reason, in this tutorial we define views as callables that
accept ``context`` in the callable argument list. If you do
@@ -26,45 +30,91 @@ assumed to return a :term:`response` object.
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
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/views/
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/views/>`_.
Declaring Dependencies in Our ``setup.py`` File
===============================================
The view code in our application will depend on a package which is not a
dependency of the original "tutorial" application. The original "tutorial"
-application was generated by the ``paster create`` command; it doesn't know
-about our custom application requirements. We need to add a dependency on
-the ``docutils`` package to our ``tutorial`` package's ``setup.py`` file by
-assigning this dependency to the ``install_requires`` parameter in the
-``setup`` function.
+application was generated by the ``pcreate`` command; it doesn't know
+about our custom application requirements.
-Our resulting ``setup.py`` should look like so:
+We need to add a dependency on the ``docutils`` package to our ``tutorial``
+package's ``setup.py`` file by assigning this dependency to the ``requires``
+parameter in the ``setup()`` function.
+
+Open ``setup.py`` and edit it to look like the following:
.. literalinclude:: src/views/setup.py
:linenos:
+ :emphasize-lines: 20
:language: python
-.. note:: After these new dependencies are added, you will need to
- rerun ``python setup.py develop`` inside the root of the
- ``tutorial`` package to obtain and register the newly added
- dependency package.
+Only the highlighted line needs to be added.
+
+
+Running ``pip install -e .``
+============================
+
+Since a new software dependency was added, you will need to run ``pip install
+-e .`` again inside the root of the ``tutorial`` package to obtain and register
+the newly added dependency distribution.
+
+Make sure your current working directory is the root of the project (the
+directory in which ``setup.py`` lives) and execute the following command.
+
+On UNIX:
+
+.. code-block:: bash
+
+ $ cd tutorial
+ $ $VENV/bin/pip install -e .
+
+On Windows:
+
+.. code-block:: doscon
+
+ c:\pyramidtut> cd tutorial
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
+
+Success executing this command will end with a line to the console something
+like:
+
+.. code-block:: text
+
+ Successfully installed docutils-0.12 tutorial-0.0
-Adding View Functions
-=====================
-We're going to add four :term:`view callable` functions to our ``views.py``
-module. One view named ``view_wiki`` will display the wiki itself (it will
-answer on the root URL), another named ``view_page`` will display an
-individual page, another named ``add_page`` will allow a page to be added,
-and a final view named ``edit_page`` will allow a page to be edited.
+Adding view functions in ``views.py``
+=====================================
+
+It's time for a major change. Open ``tutorial/views.py`` and edit it to look
+like the following:
+
+.. literalinclude:: src/views/tutorial/views.py
+ :linenos:
+ :language: python
+
+We added some imports and created a regular expression to find "WikiWords".
+
+We got rid of the ``my_view`` view function and its decorator that was added
+when we originally rendered the ``zodb`` scaffold. It was only an example and
+isn't relevant to our application.
+
+Then we added four :term:`view callable` functions to our ``views.py``
+module:
+
+* ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL.
+* ``view_page()`` - Displays an individual page.
+* ``add_page()`` - Allows the user to add a page.
+* ``edit_page()`` - Allows the user to edit a page.
+
+We'll describe each one briefly in the following sections.
.. note::
There is nothing special about the filename ``views.py``. A project may
- have many view callables throughout its codebase in arbitrarily-named
+ have many view callables throughout its codebase in arbitrarily named
files. Files implementing view callables often have ``view`` in their
filenames (or may live in a Python subpackage of your application package
named ``views``), but this is only by convention.
@@ -72,45 +122,72 @@ and a final view named ``edit_page`` will allow a page to be edited.
The ``view_wiki`` view function
-------------------------------
-The ``view_wiki`` function will be configured to respond as the default view
-callable for a Wiki resource. We'll provide it with a ``@view_config``
-decorator which names the class ``tutorial.models.Wiki`` as its context.
-This means that when a Wiki resource is the context, and no :term:`view name`
-exists in the request, this view will be used. The view configuration
-associated with ``view_wiki`` does not use a ``renderer`` because the view
-callable always returns a :term:`response` object rather than a dictionary.
-No renderer is necessary when a view returns a response object.
-
-The ``view_wiki`` view callable always redirects to the URL of a Page
-resource named "FrontPage". To do so, it returns an instance of the
+Following is the code for the ``view_wiki`` view function and its decorator:
+
+.. literalinclude:: src/views/tutorial/views.py
+ :lines: 12-14
+ :lineno-start: 12
+ :linenos:
+ :language: python
+
+.. note:: In our code, we use an *import* that is *relative* to our package
+ named ``tutorial``, meaning we can omit the name of the package in the
+ ``import`` and ``context`` statements. In our narrative, however, we refer
+ to a *class* and thus we use the *absolute* form, meaning that the name of
+ the package is included.
+
+``view_wiki()`` is the :term:`default view` that gets called when a request is
+made to the root URL of our wiki. It always redirects to an URL which
+represents the path to our "FrontPage".
+
+We provide it with a ``@view_config`` decorator which names the class
+``tutorial.models.Wiki`` as its context. This means that when a Wiki resource
+is the context and no :term:`view name` exists in the request, then this view
+will be used. The view configuration associated with ``view_wiki`` does not
+use a ``renderer`` because the view callable always returns a :term:`response`
+object rather than a dictionary. No renderer is necessary when a view returns
+a response object.
+
+The ``view_wiki`` view callable always redirects to the URL of a Page resource
+named "FrontPage". To do so, it returns an instance of the
:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
-the WebOb :term:`response` interface). The :func:`pyramid.url.resource_url`
-API. :func:`pyramid.url.resource_url` constructs a URL to the ``FrontPage``
-page resource (e.g. ``http://localhost:6543/FrontPage``), and uses it as the
-"location" of the HTTPFound response, forming an HTTP redirect.
+the :class:`pyramid.interfaces.IResponse` interface, like
+:class:`pyramid.response.Response` does). It uses the
+:meth:`pyramid.request.Request.route_url` API to construct an URL to the
+``FrontPage`` page resource (i.e., ``http://localhost:6543/FrontPage``), and
+uses it as the "location" of the ``HTTPFound`` response, forming an HTTP
+redirect.
The ``view_page`` view function
-------------------------------
-The ``view_page`` function will be configured to respond as the default view
-of a Page resource. We'll provide it with a ``@view_config`` decorator which
+Here is the code for the ``view_page`` view function and its decorator:
+
+.. literalinclude:: src/views/tutorial/views.py
+ :lines: 16-33
+ :lineno-start: 16
+ :linenos:
+ :language: python
+
+The ``view_page`` function is configured to respond as the default view
+of a Page resource. We provide it with a ``@view_config`` decorator which
names the class ``tutorial.models.Page`` as its context. This means that
when a Page resource is the context, and no :term:`view name` exists in the
request, this view will be used. We inform :app:`Pyramid` this view will use
the ``templates/view.pt`` template file as a ``renderer``.
-The ``view_page`` function generates the :term:`ReStructuredText` body of a
+The ``view_page`` function generates the :term:`reStructuredText` body of a
page (stored as the ``data`` attribute of the context passed to the view; the
-context will be a Page resource) as HTML. Then it substitutes an HTML anchor
-for each *WikiWord* reference in the rendered HTML using a compiled regular
-expression.
+context will be a ``Page`` resource) as HTML. Then it substitutes an HTML
+anchor for each *WikiWord* reference in the rendered HTML using a compiled
+regular expression.
The curried function named ``check`` is used as the first argument to
``wikiwords.sub``, indicating that it should be called to provide a value for
each WikiWord match found in the content. If the wiki (our page's
``__parent__``) already contains a page with the matched WikiWord name, the
``check`` function generates a view link to be used as the substitution value
-and returns it. If the wiki does not already contain a page with with the
+and returns it. If the wiki does not already contain a page with the
matched WikiWord name, the function generates an "add" link as the
substitution value and returns it.
@@ -118,8 +195,8 @@ As a result, the ``content`` variable is now a fully formed bit of HTML
containing various view and add links for WikiWords based on the content of
our current page resource.
-We then generate an edit URL (because it's easier to do here than in the
-template), and we wrap up a number of arguments in a dictionary and return
+We then generate an edit URL because it's easier to do here than in the
+template, and we wrap up a number of arguments in a dictionary and return
it.
The arguments we wrap into a dictionary include ``page``, ``content``, and
@@ -138,15 +215,23 @@ callable. In the ``view_wiki`` view callable, we unconditionally return a
The ``add_page`` view function
------------------------------
-The ``add_page`` function will be configured to respond when the context
-resource is a Wiki and the :term:`view name` is ``add_page``. We'll provide
-it with a ``@view_config`` decorator which names the string ``add_page`` as
-its :term:`view name` (via name=), the class ``tutorial.models.Wiki`` as its
-context, and the renderer named ``templates/edit.pt``. This means that when
-a Wiki resource is the context, and a :term:`view name` named ``add_page``
+Here is the code for the ``add_page`` view function and its decorator:
+
+.. literalinclude:: src/views/tutorial/views.py
+ :lines: 35-50
+ :lineno-start: 35
+ :linenos:
+ :language: python
+
+The ``add_page`` function is configured to respond when the context resource
+is a Wiki and the :term:`view name` is ``add_page``. We provide it with a
+``@view_config`` decorator which names the string ``add_page`` as its
+:term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its
+context, and the renderer named ``templates/edit.pt``. This means that when a
+Wiki resource is the context, and a :term:`view name` named ``add_page``
exists as the result of traversal, this view will be used. We inform
-:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as
-a ``renderer``. We share the same template between add and edit views, thus
+:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a
+``renderer``. We share the same template between add and edit views, thus
``edit.pt`` instead of ``add.pt``.
The ``add_page`` function will be invoked when a user clicks on a WikiWord
@@ -159,7 +244,7 @@ Page resource).
The request :term:`subpath` in :app:`Pyramid` is the sequence of names that
are found *after* the :term:`view name` in the URL segments given in the
``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. If our
-add view is invoked via, e.g. ``http://localhost:6543/add_page/SomeName``,
+add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``,
the :term:`subpath` will be a tuple: ``('SomeName',)``.
The add view takes the zeroth element of the subpath (the wiki page name),
@@ -169,14 +254,14 @@ we're trying to add.
If the view rendering is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
renders a template. To do so, it generates a "save url" which the template
-use as the form post URL during rendering. We're lazy here, so we're trying
+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. To do so, we create a dummy Page resource object in
order to satisfy the edit form's desire to have *some* page object exposed as
``page``, and we'll render the template to a response.
If the view rendering *is* a result of a form submission (if the expression
-``'form.submitted' in request.params`` is ``True``), we scrape the page body
+``'form.submitted' in request.params`` is ``True``), we grab the page body
from the form data, create a Page object using the name in the subpath and
the page body, and save it into "our context" (the Wiki) using the
``__setitem__`` method of the context. We then redirect back to the
@@ -185,8 +270,16 @@ the page body, and save it into "our context" (the Wiki) using the
The ``edit_page`` view function
-------------------------------
-The ``edit_page`` function will be configured to respond when the context is
-a Page resource and the :term:`view name` is ``edit_page``. We'll provide it
+Here is the code for the ``edit_page`` view function and its decorator:
+
+.. literalinclude:: src/views/tutorial/views.py
+ :lines: 52-60
+ :lineno-start: 52
+ :linenos:
+ :language: python
+
+The ``edit_page`` function is configured to respond when the context is
+a Page resource and the :term:`view name` is ``edit_page``. We provide it
with a ``@view_config`` decorator which names the string ``edit_page`` as its
:term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its
context, and the renderer named ``templates/edit.pt``. This means that when
@@ -211,110 +304,95 @@ If the view execution *is* a result of a form submission (if the expression
attribute of the page context. It then redirects to the default view of the
context (the page), which will always be the ``view_page`` view.
-Viewing the Result of all Our Edits to ``views.py``
-===================================================
+Adding templates
+================
-The result of all of our edits to ``views.py`` will leave it looking like
-this:
+The ``view_page``, ``add_page`` and ``edit_page`` views that we've added
+reference a :term:`template`. Each template is a :term:`Chameleon`
+:term:`ZPT` template. These templates will live in the ``templates``
+directory of our tutorial package. Chameleon templates must have a ``.pt``
+extension to be recognized as such.
-.. literalinclude:: src/views/tutorial/views.py
- :linenos:
- :language: python
+The ``view.pt`` template
+------------------------
-Adding Templates
-================
+Create ``tutorial/templates/view.pt`` and add the following
+content:
+
+.. literalinclude:: src/views/tutorial/templates/view.pt
+ :linenos:
+ :language: html
-Most view callables we've added expected to be rendered via a
-:term:`template`. The default templating systems in :app:`Pyramid` are
-:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of :term:`ZPT`,
-which is an XML-based templating language. Mako is a non-XML-based
-templating language. Because we had to pick one, we chose Chameleon for this
-tutorial.
+This template is used by ``view_page()`` for displaying a single
+wiki page. It includes:
-The templates we create will live in the ``templates`` directory of our
-tutorial package. Chameleon templates must have a ``.pt`` extension to be
-recognized as such.
+- A ``div`` element that is replaced with the ``content`` value provided by
+ the view (lines 36-38). ``content`` contains HTML, so the ``structure``
+ keyword is used to prevent escaping it (i.e., changing ">" to "&gt;", etc.)
+- A link that points at the "edit" URL which invokes the ``edit_page`` view
+ for the page being viewed (lines 40-42).
-The ``view.pt`` Template
+The ``edit.pt`` template
------------------------
-The ``view.pt`` template is used for viewing a single Page. It is used by
-the ``view_page`` view function. It should have a div that is "structure
-replaced" with the ``content`` value provided by the view. It should also
-have a link on the rendered page that points at the "edit" URL (the URL which
-invokes the ``edit_page`` view for the page being viewed).
+Create ``tutorial/templates/edit.pt`` and add the following content:
-Once we're done with the ``view.pt`` template, it will look a lot like
-the below:
+.. literalinclude:: src/views/tutorial/templates/edit.pt
+ :linenos:
+ :language: html
-.. literalinclude:: src/views/tutorial/templates/view.pt
- :language: xml
-
-.. note:: The names available for our use in a template are always those that
- are present in the dictionary returned by the view callable. But our
- templates make use of a ``request`` object that none of our tutorial views
- return in their dictionary. This value appears as if "by magic".
- However, ``request`` is one of several names that are available "by
- default" in a template when a template renderer is used. See
- :ref:`chameleon_template_renderers` for more information about other names
- that are available by default in a template when a template is used as a
- renderer.
-
-The ``edit.pt`` Template
-------------------------
+This template is used by ``add_page()`` and ``edit_page()`` for adding and
+editing a wiki page. It displays a page containing a form that includes:
-The ``edit.pt`` template is used for adding and editing a Page. It is used
-by the ``add_page`` and ``edit_page`` view functions. It should display a
-page containing a form that POSTs back to the "save_url" argument supplied by
-the view. The form should have a "body" textarea field (the page data), and
-a submit button that has the name "form.submitted". The textarea in the form
-should be filled with any existing page data when it is rendered.
+- A 10 row by 60 column ``textarea`` field named ``body`` that is filled
+ with any existing page data when it is rendered (line 45).
+- A submit button that has the name ``form.submitted`` (line 48).
-Once we're done with the ``edit.pt`` template, it will look a lot like the
-below:
+The form POSTs back to the ``save_url`` argument supplied by the view (line
+43). The view will use the ``body`` and ``form.submitted`` values.
-.. literalinclude:: src/views/tutorial/templates/edit.pt
- :language: xml
+.. note:: Our templates use a ``request`` object that none of our tutorial
+ views return in their dictionary. ``request`` is one of several names that
+ are available "by default" in a template when a template renderer is used.
+ See :ref:`renderer_system_values` for information about other names that
+ are available by default when a template is used as a renderer.
-Static Assets
+
+Static assets
-------------
-Our templates name a single static asset named ``pylons.css``. We don't need
-to create this file within our package's ``static`` directory because it was
-provided at the time we created the project. This file is a little too long to
-replicate within the body of this guide, however it is available `online
-<http://github.com/Pylons/pyramid/blob/master/docs/tutorials/wiki/src/views/tutorial/static/pylons.css>`_.
+Our templates name static assets, including CSS and images. We don't need
+to create these files within our package's ``static`` directory because they
+were provided at the time we created the project.
-This CSS file will be accessed via
-e.g. ``http://localhost:6543/static/pylons.css`` by virtue of the call to
+As an example, the CSS file will be accessed via
+``http://localhost:6543/static/theme.css`` by virtue of the call to the
``add_static_view`` directive we've made in the ``__init__.py`` file. Any
number and type of static assets can be placed in this directory (or
subdirectories) and are just referred to by URL or by using the convenience
-method ``static_url`` e.g. ``request.static_url('{{package}}:static/foo.css')``
-within templates.
+method ``static_url``, e.g.,
+``request.static_url('<package>:static/foo.css')`` within templates.
+
-Viewing the Application in a Browser
+Viewing the application in a browser
====================================
-We can finally examine our application in a
-browser. The views we'll try are as follows:
+We can finally examine our application in a browser (See
+:ref:`wiki-start-the-application`). Launch a browser and visit
+each of the following URLs, checking that the result is as expected:
-- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki``
- view. This always redirects to the ``view_page`` view of the ``FrontPage``
- Page resource.
+- http://localhost:6543/ invokes the ``view_wiki`` view. This always
+ redirects to the ``view_page`` view of the ``FrontPage`` Page resource.
-- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes
- the ``view_page`` view of the front page resource. This is
- because it's the *default view* (a view without a ``name``) for Page
- resources.
+- http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front
+ page resource. This is because it's the :term:`default view` (a view
+ without a ``name``) for Page resources.
-- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser
- invokes the edit view for the ``FrontPage`` Page resource.
+- http://localhost:6543/FrontPage/edit_page invokes the edit view for the
+ ``FrontPage`` Page resource.
-- Visiting ``http://localhost:6543/add_page/SomePageName`` in a
- browser invokes the add view for a Page.
+- http://localhost:6543/add_page/SomePageName invokes the add view for a Page.
-- To generate an error, visit ``http://localhost:6543/add_page`` which
- will generate an ``IndexError`` for the expression
- ``request.subpath[0]``. You'll see an interactive traceback
- facility provided by :term:`WebError`.
+- To generate an error, visit http://localhost:6543/add_page which will
+ generate an ``IndexErrorr: tuple index out of range`` error. You'll see an
+ interactive traceback facility provided by :term:`pyramid_debugtoolbar`.
diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst
new file mode 100644
index 000000000..f2a02176b
--- /dev/null
+++ b/docs/tutorials/wiki/design.rst
@@ -0,0 +1,151 @@
+.. _wiki_design:
+
+======
+Design
+======
+
+Following is a quick overview of the design of our wiki application, to help
+us understand the changes that we will be making as we work through the
+tutorial.
+
+Overall
+-------
+
+We choose to use :term:`reStructuredText` markup in the wiki text. Translation
+from reStructuredText to HTML is provided by the widely used ``docutils``
+Python module. We will add this module in the dependency list on the project
+``setup.py`` file.
+
+Models
+------
+
+The root resource named ``Wiki`` will be a mapping of wiki page
+names to page resources. The page resources will be instances
+of a *Page* class and they store the text content.
+
+URLs like ``/PageName`` will be traversed using Wiki[
+*PageName* ] => page, and the context that results is the page
+resource of an existing page.
+
+To add a page to the wiki, a new instance of the page resource
+is created and its name and reference are added to the Wiki
+mapping.
+
+A page named ``FrontPage`` containing the text *This is the front page*, will
+be created when the storage is initialized, and will be used as the wiki home
+page.
+
+Views
+-----
+
+There will be three views to handle the normal operations of adding,
+editing, and viewing wiki pages, plus one view for the wiki front page.
+Two templates will be used, one for viewing, and one for both adding
+and editing wiki pages.
+
+The default templating systems in :app:`Pyramid` are
+:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of
+:term:`ZPT`, which is an XML-based templating language. Mako is a
+non-XML-based templating language. Because we had to pick one,
+we chose Chameleon for this tutorial.
+
+Security
+--------
+
+We'll eventually be adding security to our application. The components we'll
+use to do this are below.
+
+- USERS, a dictionary mapping :term:`userids <userid>` to their
+ corresponding passwords.
+
+- GROUPS, a dictionary mapping :term:`userids <userid>` to a
+ list of groups to which they belong.
+
+- ``groupfinder``, an *authorization callback* that looks up USERS and
+ GROUPS. It will be provided in a new ``security.py`` file.
+
+- An :term:`ACL` is attached to the root :term:`resource`. Each row below
+ details an :term:`ACE`:
+
+ +----------+----------------+----------------+
+ | Action | Principal | Permission |
+ +==========+================+================+
+ | Allow | Everyone | View |
+ +----------+----------------+----------------+
+ | Allow | group:editors | Edit |
+ +----------+----------------+----------------+
+
+- Permission declarations are added to the views to assert the security
+ policies as each request is handled.
+
+Two additional views and one template will handle the login and
+logout tasks.
+
+Summary
+-------
+
+The URL, context, actions, template and permission associated to each view are
+listed in the following table:
+
++----------------------+-------------+-----------------+-----------------------+------------+------------+
+| URL | View | Context | Action | Template | Permission |
+| | | | | | |
++======================+=============+=================+=======================+============+============+
+| / | view_wiki | Wiki | Redirect to | | |
+| | | | /FrontPage | | |
++----------------------+-------------+-----------------+-----------------------+------------+------------+
+| /PageName | view_page | Page | Display existing | view.pt | view |
+| | [1]_ | | page [2]_ | | |
+| | | | | | |
+| | | | | | |
+| | | | | | |
++----------------------+-------------+-----------------+-----------------------+------------+------------+
+| /PageName/edit_page | edit_page | Page | Display edit form | edit.pt | edit |
+| | | | with existing | | |
+| | | | content. | | |
+| | | | | | |
+| | | | If the form was | | |
+| | | | submitted, redirect | | |
+| | | | to /PageName | | |
++----------------------+-------------+-----------------+-----------------------+------------+------------+
+| /add_page/PageName | add_page | Wiki | Create the page | edit.pt | edit |
+| | | | *PageName* in | | |
+| | | | storage, display | | |
+| | | | the edit form | | |
+| | | | without content. | | |
+| | | | | | |
+| | | | If the form was | | |
+| | | | submitted, | | |
+| | | | redirect to | | |
+| | | | /PageName | | |
++----------------------+-------------+-----------------+-----------------------+------------+------------+
+| /login | login | Wiki, | Display login form. | login.pt | |
+| | | Forbidden [3]_ | | | |
+| | | | If the form was | | |
+| | | | submitted, | | |
+| | | | authenticate. | | |
+| | | | | | |
+| | | | - If authentication | | |
+| | | | succeeds, | | |
+| | | | redirect to the | | |
+| | | | page that we | | |
+| | | | came from. | | |
+| | | | | | |
+| | | | - If authentication | | |
+| | | | fails, display | | |
+| | | | login form with | | |
+| | | | "login failed" | | |
+| | | | message. | | |
+| | | | | | |
++----------------------+-------------+-----------------+-----------------------+------------+------------+
+| /logout | logout | Wiki | Redirect to | | |
+| | | | /FrontPage | | |
++----------------------+-------------+-----------------+-----------------------+------------+------------+
+
+.. [1] This is the default view for a Page context
+ when there is no view name.
+.. [2] Pyramid will return a default 404 Not Found page
+ if the page *PageName* does not exist yet.
+.. [3] ``pyramid.exceptions.Forbidden`` is reached when a
+ user tries to invoke a view that is
+ not authorized by the authorization policy.
diff --git a/docs/tutorials/wiki/distributing.rst b/docs/tutorials/wiki/distributing.rst
index ed0af222f..c3037f396 100644
--- a/docs/tutorials/wiki/distributing.rst
+++ b/docs/tutorials/wiki/distributing.rst
@@ -1,42 +1,41 @@
+.. _wiki_distributing_your_application:
+
=============================
Distributing Your Application
=============================
-Once your application works properly, you can create a "tarball" from
-it by using the ``setup.py sdist`` command. The following commands
-assume your current working directory is the ``tutorial`` package
-we've created and that the parent directory of the ``tutorial``
-package is a virtualenv representing a :app:`Pyramid` environment.
+Once your application works properly, you can create a "tarball" from it by
+using the ``setup.py sdist`` command. The following commands assume your
+current working directory is the ``tutorial`` package we've created and that
+the parent directory of the ``tutorial`` package is a virtual environment
+representing a :app:`Pyramid` environment.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
- $ ../bin/python setup.py sdist
+ $ $VENV/bin/python setup.py sdist
On Windows:
-.. code-block:: text
+.. code-block:: doscon
- c:\pyramidtut> ..\Scripts\python setup.py sdist
+ c:\pyramidtut> %VENV%\Scripts\python setup.py sdist
The output of such a command will be something like:
.. code-block:: text
running sdist
- # .. more output ..
+ # more output
creating dist
- tar -cf dist/tutorial-0.1.tar tutorial-0.1
- gzip -f9 dist/tutorial-0.1.tar
- removing 'tutorial-0.1' (and everything under it)
-
-Note that this command creates a tarball in the "dist" subdirectory
-named ``tutorial-0.1.tar.gz``. You can send this file to your friends
-to show them your cool new application. They should be able to
-install it by pointing the ``easy_install`` command directly at it.
-Or you can upload it to `PyPI <http://pypi.python.org>`_ and share it
-with the rest of the world, where it can be downloaded via
-``easy_install`` remotely like any other package people download from
-PyPI.
-
+ Creating tar archive
+ removing 'tutorial-0.0' (and everything under it)
+
+Note that this command creates a tarball in the "dist" subdirectory named
+``tutorial-0.0.tar.gz``. You can send this file to your friends to show them
+your cool new application. They should be able to install it by pointing the
+``pip install .`` command directly at it. Or you can upload it to `PyPI
+<http://pypi.python.org>`_ and share it with the rest of the world, where it
+can be downloaded via ``pip install`` remotely like any other package people
+download from PyPI.
diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst
index 3edc6ba04..7808c7623 100644
--- a/docs/tutorials/wiki/index.rst
+++ b/docs/tutorials/wiki/index.rst
@@ -3,21 +3,22 @@
ZODB + Traversal Wiki Tutorial
==============================
-This tutorial introduces a :term:`traversal` -based :app:`Pyramid`
-application to a developer familiar with Python. It will be most familiar to
-developers with previous :term:`Zope` experience. When we're done with the
-tutorial, the developer will have created a basic Wiki application with
+This tutorial introduces a :term:`ZODB` and :term:`traversal`-based
+:app:`Pyramid` application to a developer familiar with Python. It will be
+most familiar to developers with previous :term:`Zope` experience. When the
+is finished, the developer will have created a 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/wiki/src/
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/>`_.
+tutorial can be browsed on GitHub at `docs/tutorials/wiki/src
+<https://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src>`_,
+which corresponds to the same location if you have Pyramid sources.
.. toctree::
:maxdepth: 2
background
+ design
installation
basiclayout
definingmodels
@@ -25,4 +26,3 @@ tutorial can be browsed at
authorization
tests
distributing
-
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index 30fb67441..dbf995595 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -1,269 +1,394 @@
+.. _wiki_installation:
+
============
Installation
============
-For the most part, the installation process for this tutorial
-duplicates the steps described in :ref:`installing_chapter` and
-:ref:`project_narr`, however it also explains how to install
-additional libraries for tutorial purposes.
+Before you begin
+----------------
+
+This tutorial assumes that you have already followed the steps in
+:ref:`installing_chapter`, except **do not create a virtual environment or
+install Pyramid**. Thereby you will satisfy the following requirements.
+
+* A Python interpreter is installed on your operating system.
+* You've satisfied the :ref:`requirements-for-installing-packages`.
+
-Preparation
-========================
+Create directory to contain the project
+---------------------------------------
-Please take the following steps to prepare for the tutorial. The
-steps to prepare for the tutorial are slightly different depending on
-whether you're using UNIX or Windows.
+We need a workspace for our project files.
-Preparation, UNIX
------------------
+On UNIX
+^^^^^^^
-#. If you don't already have a Python 2.6 interpreter installed on
- your system, obtain, install, or find `Python 2.6
- <http://python.org/download/releases/2.6.6/>`_ for your system.
+.. code-block:: bash
-#. Make sure the Python development headers are installed on your system. If
- you've installed Python from source, these will already be installed. If
- you're using a system Python, you may have to install a ``python-dev``
- package (e.g. ``apt-get python-dev``). The headers are not required for
- Pyramid itself, just for dependencies of the tutorial.
+ $ mkdir ~/pyramidtut
-#. Install the latest `setuptools` into the Python you
- obtained/installed/found in the step above: download `ez_setup.py
- <http://peak.telecommunity.com/dist/ez_setup.py>`_ and run it using
- the ``python`` interpreter of your Python 2.6 installation:
+On Windows
+^^^^^^^^^^
- .. code-block:: text
+.. code-block:: doscon
- $ /path/to/my/Python-2.6/bin/python ez_setup.py
+ c:\> mkdir pyramidtut
-#. Use that Python's `bin/easy_install` to install `virtualenv`:
- .. code-block:: text
+Create and use a virtual Python environment
+-------------------------------------------
- $ /path/to/my/Python-2.6/bin/easy_install virtualenv
+Next let's create a virtual environment workspace for our project. We will use
+the ``VENV`` environment variable instead of the absolute path of the virtual
+environment.
-#. Use that Python's virtualenv to make a workspace:
+On UNIX
+^^^^^^^
- .. code-block:: text
+.. code-block:: bash
- $ path/to/my/Python-2.6/bin/virtualenv --no-site-packages \
- pyramidtut
+ $ export VENV=~/pyramidtut
+ $ python3 -m venv $VENV
-#. Switch to the ``pyramidtut`` directory:
+On Windows
+^^^^^^^^^^
- .. code-block:: text
+.. code-block:: doscon
- $ cd pyramidtut
+ c:\> set VENV=c:\pyramidtut
-#. (Optional) Consider using ``source bin/activate`` to make your
- shell environment wired to use the virtualenv.
+Each version of Python uses different paths, so you will need to adjust the
+path to the command for your Python version.
-#. Use ``easy_install`` to get :app:`Pyramid` and its direct
- dependencies installed:
+Python 2.7:
- .. code-block:: text
+.. code-block:: doscon
- $ bin/easy_install pyramid
+ c:\> c:\Python27\Scripts\virtualenv %VENV%
-#. Use ``easy_install`` to install ``docutils``, ``repoze.tm2``,
- ``repoze.zodbconn``, ``nose`` and ``coverage``:
+Python 3.5:
- .. code-block:: text
+.. code-block:: doscon
- $ bin/easy_install docutils repoze.tm2 repoze.zodbconn \
- nose coverage
+ c:\> c:\Python35\Scripts\python -m venv %VENV%
-Preparation, Windows
---------------------
-#. Install, or find `Python 2.6
- <http://python.org/download/releases/2.6.6/>`_ for your system.
+Upgrade ``pip`` and ``setuptools`` in the virtual environment
+-------------------------------------------------------------
-#. Install the latest `setuptools` into the Python you
- obtained/installed/found in the step above: download `ez_setup.py
- <http://peak.telecommunity.com/dist/ez_setup.py>`_ and run it using
- the ``python`` interpreter of your Python 2.6 installation using a
- command prompt:
+On UNIX
+^^^^^^^
- .. code-block:: text
+.. code-block:: bash
- c:\> c:\Python26\python ez_setup.py
+ $ $VENV/bin/pip install --upgrade pip setuptools
-#. Use that Python's `bin/easy_install` to install `virtualenv`:
+On Windows
+^^^^^^^^^^
- .. code-block:: text
+.. code-block:: doscon
- c:\> c:\Python26\Scripts\easy_install virtualenv
+ c:\> %VENV%\Scripts\pip install --upgrade pip setuptools
-#. Use that Python's virtualenv to make a workspace:
- .. code-block:: text
+Install Pyramid into the virtual Python environment
+---------------------------------------------------
- c:\> c:\Python26\Scripts\virtualenv --no-site-packages pyramidtut
+On UNIX
+^^^^^^^
-#. Switch to the ``pyramidtut`` directory:
+.. code-block:: bash
- .. code-block:: text
+ $ $VENV/bin/pip install pyramid
- c:\> cd pyramidtut
+On Windows
+^^^^^^^^^^
-#. (Optional) Consider using ``bin\activate.bat`` to make your shell
- environment wired to use the virtualenv.
+.. code-block:: doscon
-#. Use ``easy_install`` to get :app:`Pyramid` and its direct
- dependencies installed:
+ c:\> %VENV%\Scripts\pip install pyramid
- .. code-block:: text
+Change directory to your virtual Python environment
+---------------------------------------------------
- c:\pyramidtut> Scripts\easy_install pyramid
+Change directory to the ``pyramidtut`` directory, which is both your workspace
+and your virtual environment.
-#. Use ``easy_install`` to install ``docutils``, ``repoze.tm2``,
- ``repoze.zodbconn``, ``nose`` and ``coverage``:
+On UNIX
+^^^^^^^
- .. code-block:: text
+.. code-block:: bash
- c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 ^
- repoze.zodbconn nose coverage
+ $ cd pyramidtut
+
+On Windows
+^^^^^^^^^^
+
+.. code-block:: doscon
+
+ c:\> cd pyramidtut
.. _making_a_project:
-Making a Project
-================
+Making a project
+----------------
-Your next step is to create a project. :app:`Pyramid` supplies a
-variety of scaffolds to generate sample projects. For this tutorial,
-we will use the :term:`ZODB` -oriented scaffold named ``pyramid_zodb``.
+Your next step is to create a project. For this tutorial, we will use
+the :term:`scaffold` named ``zodb``, which generates an application
+that uses :term:`ZODB` and :term:`traversal`.
-The below instructions assume your current working directory is the
-"virtualenv" named "pyramidtut".
+:app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We
+will use ``pcreate``, a script that comes with Pyramid, to create our project
+using a scaffold.
-On UNIX:
+By passing ``zodb`` into the ``pcreate`` command, the script creates the files
+needed to use ZODB. By passing in our application name ``tutorial``, the script
+inserts that application name into all the required files.
-.. code-block:: text
+The below instructions assume your current working directory is "pyramidtut".
- $ bin/paster create -t pyramid_zodb tutorial
+On UNIX
+^^^^^^^
-On Windows:
+.. code-block:: bash
-.. code-block:: text
+ $ $VENV/bin/pcreate -s zodb tutorial
- c:\pyramidtut> Scripts\paster create -t pyramid_zodb tutorial
+On Windows
+^^^^^^^^^^
-.. note:: If you are using Windows, the ``pyramid_zodb`` Paster scaffold
- doesn't currently deal gracefully with installation into a location
- that contains spaces in the path. If you experience startup
- problems, try putting both the virtualenv and the project into
- directories that do not contain spaces in their paths.
+.. code-block:: doscon
-Installing the Project in "Development Mode"
-============================================
+ c:\pyramidtut> %VENV%\Scripts\pcreate -s zodb tutorial
-In order to do development on the project easily, you must "register"
-the project as a development egg in your workspace using the
-``setup.py develop`` command. In order to do so, cd to the "tutorial"
-directory you created in :ref:`making_a_project`, and run the
-"setup.py develop" command using virtualenv Python interpreter.
+.. note:: If you are using Windows, the ``zodb`` scaffold may not deal
+ gracefully with installation into a location that contains spaces in the
+ path. If you experience startup problems, try putting both the virtual
+ environment and the project into directories that do not contain spaces in
+ their paths.
-On UNIX:
-.. code-block:: text
+.. _installing_project_in_dev_mode_zodb:
- $ cd tutorial
- $ ../bin/python setup.py develop
+Installing the project in development mode
+------------------------------------------
-On Windows:
+In order to do development on the project easily, you must "register" the
+project as a development egg in your workspace using the ``pip install -e .``
+command. In order to do so, change directory to the ``tutorial`` directory that
+you created in :ref:`making_a_project`, and run the ``pip install -e .``
+command using the virtual environment Python interpreter.
-.. code-block:: text
+On UNIX
+^^^^^^^
+
+.. code-block:: bash
+
+ $ cd tutorial
+ $ $VENV/bin/pip install -e .
+
+On Windows
+^^^^^^^^^^
+
+.. code-block:: doscon
+
+ c:\pyramidtut> cd tutorial
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
+
+The console will show ``pip`` checking for packages and installing missing
+packages. Success executing this command will show a line like the following:
+
+.. code-block:: bash
+
+ Successfully installed BTrees-4.2.0 Chameleon-2.24 Mako-1.0.4 \
+ MarkupSafe-0.23 Pygments-2.1.3 ZConfig-3.1.0 ZEO-4.2.0b1 ZODB-4.2.0 \
+ ZODB3-3.11.0 mock-2.0.0 pbr-1.8.1 persistent-4.1.1 pyramid-chameleon-0.3 \
+ pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 pyramid-tm-0.12.1 \
+ pyramid-zodbconn-0.7 six-1.10.0 transaction-1.4.4 tutorial waitress-0.8.10 \
+ zc.lockfile-1.1.0 zdaemon-4.1.0 zodbpickle-0.6.0 zodburi-2.0
+
+
+.. _install-testing-requirements_zodb:
+
+Install testing requirements
+----------------------------
+
+In order to run tests, we need to install the testing requirements. This is
+done through our project's ``setup.py`` file, in the ``tests_require`` and
+``extras_require`` stanzas, and by issuing the command below for your
+operating system.
+
+.. literalinclude:: src/installation/setup.py
+ :language: python
+ :linenos:
+ :lineno-start: 22
+ :lines: 22-26
+
+.. literalinclude:: src/installation/setup.py
+ :language: python
+ :linenos:
+ :lineno-start: 45
+ :lines: 45-47
+
+On UNIX
+^^^^^^^
+
+.. code-block:: bash
+
+ $ $VENV/bin/pip install -e ".[testing]"
+
+On Windows
+^^^^^^^^^^
+
+.. code-block:: doscon
+
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e ".[testing]"
- C:\pyramidtut> cd tutorial
- C:\pyramidtut\tutorial> ..\Scripts\python setup.py develop
.. _running_tests:
-Running the Tests
-=================
+Run the tests
+-------------
-After you've installed the project in development mode, you may run
-the tests for the project.
+After you've installed the project in development mode as well as the testing
+requirements, you may run the tests for the project.
-On UNIX:
+On UNIX
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
- $ ../bin/python setup.py test -q
+ $ $VENV/bin/py.test tutorial/tests.py -q
-On Windows:
+On Windows
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
+
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test tutorial\tests.py -q
+
+For a successful test run, you should see output that ends like this:
+
+.. code-block:: bash
+
+ .
+ 1 passed in 0.24 seconds
+
+
+Expose test coverage information
+--------------------------------
+
+You can run the ``py.test`` command to see test coverage information. This
+runs the tests in the same way that ``py.test`` does, but provides additional
+"coverage" information, exposing which lines of your project are covered by the
+tests.
+
+We've already installed the ``pytest-cov`` package into our virtual
+environment, so we can run the tests with coverage.
+
+On UNIX
+^^^^^^^
+
+.. code-block:: bash
+
+ $ $VENV/bin/py.test --cov=tutorial --cov-report=term-missing tutorial/tests.py
+
+On Windows
+^^^^^^^^^^
+
+.. code-block:: doscon
- c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov=tutorial \
+ --cov-report=term-missing tutorial\tests.py
-Starting the Application
-========================
+If successful, you will see output something like this:
+
+.. code-block:: bash
+
+ ======================== test session starts ========================
+ platform Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
+ rootdir: /Users/stevepiercy/projects/pyramidtut/tutorial, inifile:
+ plugins: cov-2.2.1
+ collected 1 items
+
+ tutorial/tests.py .
+ ------------------ coverage: platform Python 3.5.1 ------------------
+ Name Stmts Miss Cover Missing
+ ----------------------------------------------------
+ tutorial/__init__.py 12 7 42% 7-8, 14-18
+ tutorial/models.py 10 6 40% 9-14
+ tutorial/tests.py 12 0 100%
+ tutorial/views.py 4 0 100%
+ ----------------------------------------------------
+ TOTAL 38 13 66%
+
+ ===================== 1 passed in 0.31 seconds ======================
+
+Our package doesn't quite have 100% test coverage.
+
+
+.. _wiki-start-the-application:
+
+Start the application
+---------------------
Start the application.
-On UNIX:
+On UNIX
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
- $ ../bin/paster serve development.ini --reload
+ $ $VENV/bin/pserve development.ini --reload
-On Windows:
+On Windows
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
- c:\pyramidtut\tutorial> ..\Scripts\paster serve development.ini --reload
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload
-Exposing Test Coverage Information
-==================================
+.. note::
-You can run the ``nosetests`` command to see test coverage
-information. This runs the tests in the same way that ``setup.py
-test`` does but provides additional "coverage" information, exposing
-which lines of your project are "covered" (or not covered) by the
-tests.
+ Your OS firewall, if any, may pop up a dialog asking for authorization
+ to allow python to accept incoming network connections.
-On UNIX:
+If successful, you will see something like this on your console:
.. code-block:: text
- $ ../bin/nosetests --cover-package=tutorial --cover-erase --with-coverage
+ Starting subprocess with file monitor
+ Starting server in PID 95736.
+ serving on http://127.0.0.1:6543
-On Windows:
+This means the server is ready to accept requests.
-.. code-block:: text
- c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^
- --cover-erase --with-coverage
+Visit the application in a browser
+----------------------------------
-Looks like the code in the ``pyramid_zodb`` scaffold for ZODB projects is
-missing some test coverage, particularly in the file named
-``models.py``.
+In a browser, visit http://localhost:6543/. You will see the generated
+application's default page.
-Visit the Application in a Browser
-==================================
+One thing you'll notice is the "debug toolbar" icon on right hand side of the
+page. You can read more about the purpose of the icon at
+:ref:`debug_toolbar`. It allows you to get information about your
+application while you develop.
-In a browser, visit `http://localhost:6543/ <http://localhost:6543>`_.
-You will see the generated application's default page.
-Decisions the ``pyramid_zodb`` Scaffold Has Made For You
-========================================================
+Decisions the ``zodb`` scaffold has made for you
+------------------------------------------------
-Creating a project using the ``pyramid_zodb`` scaffold makes the following
+Creating a project using the ``zodb`` scaffold makes the following
assumptions:
-- you are willing to use :term:`ZODB` as persistent storage
-
-- you are willing to use :term:`traversal` to map URLs to code.
+- You are willing to use :term:`ZODB` as persistent storage.
-- you want to use imperative code plus a :term:`scan` to perform
- configuration.
+- You are willing to use :term:`traversal` to map URLs to code.
.. note::
- :app:`Pyramid` supports any persistent storage mechanism (e.g. a SQL
- database or filesystem files, etc). :app:`Pyramid` also supports an
- additional mechanism to map URLs to code (:term:`URL dispatch`). However,
- for the purposes of this tutorial, we'll only be using traversal and ZODB.
-
+ :app:`Pyramid` supports any persistent storage mechanism (e.g., a SQL
+ database or filesystem files). It also supports an additional
+ mechanism to map URLs to code (:term:`URL dispatch`). However, for the
+ purposes of this tutorial, we'll only be using traversal and ZODB.
diff --git a/docs/tutorials/wiki/src/authorization/CHANGES.txt b/docs/tutorials/wiki/src/authorization/CHANGES.txt
index e14f633ab..35a34f332 100644
--- a/docs/tutorials/wiki/src/authorization/CHANGES.txt
+++ b/docs/tutorials/wiki/src/authorization/CHANGES.txt
@@ -1,5 +1,4 @@
0.0
---
-- Initial version
-
+- Initial version
diff --git a/docs/tutorials/wiki/src/authorization/README.txt b/docs/tutorials/wiki/src/authorization/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/authorization/README.txt
+++ b/docs/tutorials/wiki/src/authorization/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini
index 1ba746d0e..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/authorization/development.ini
+++ b/docs/tutorials/wiki/src/authorization/development.ini
@@ -1,34 +1,44 @@
-[app:tutorial]
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
use = egg:tutorial
-reload_templates = true
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = true
-default_locale_name = en
-zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
-
-[pipeline:main]
-pipeline =
- egg:WebError#evalerror
- egg:repoze.zodbconn#closer
- egg:repoze.retry#retry
- tm
- tutorial
-
-[filter:tm]
-use = egg:repoze.tm2#tm
-commit_veto = repoze.tm:default_commit_veto
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_zodbconn
+ pyramid_tm
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
[server:main]
-use = egg:Paste#http
-host = 0.0.0.0
+use = egg:waitress#main
+host = 127.0.0.1
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -40,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -47,6 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini
index 5c47ade9b..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/authorization/production.ini
+++ b/docs/tutorials/wiki/src/authorization/production.ini
@@ -1,45 +1,36 @@
-[app:tutorial]
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
use = egg:tutorial
-reload_templates = false
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = false
-default_locale_name = en
-zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
-[filter:weberror]
-use = egg:WebError#error_catcher
-debug = false
-;error_log =
-;show_exceptions_in_wsgi_errors = true
-;smtp_server = localhost
-;error_email = janitor@example.com
-;smtp_username = janitor
-;smtp_password = "janitor's password"
-;from_address = paste@localhost
-;error_subject_prefix = "Pyramid Error"
-;smtp_use_tls =
-;error_message =
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_tm
+ pyramid_zodbconn
-[filter:tm]
-use = egg:repoze.tm2#tm
-commit_veto = repoze.tm:default_commit_veto
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
-[pipeline:main]
-pipeline =
- weberror
- egg:repoze.zodbconn#closer
- egg:repoze.retry#retry
- tm
- tutorial
+###
+# wsgi server configuration
+###
[server:main]
-use = egg:Paste#http
+use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -66,6 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/authorization/setup.cfg b/docs/tutorials/wiki/src/authorization/setup.cfg
deleted file mode 100644
index 3d7ea6e23..000000000
--- a/docs/tutorials/wiki/src/authorization/setup.cfg
+++ /dev/null
@@ -1,28 +0,0 @@
-[nosetests]
-match=^test
-nocapture=1
-cover-package=tutorial
-with-coverage=1
-cover-erase=1
-
-[compile_catalog]
-directory = tutorial/locale
-domain = tutorial
-statistics = true
-
-[extract_messages]
-add_comments = TRANSLATORS:
-output_file = tutorial/locale/tutorial.pot
-width = 80
-
-[init_catalog]
-domain = tutorial
-input_file = tutorial/locale/tutorial.pot
-output_dir = tutorial/locale
-
-[update_catalog]
-domain = tutorial
-input_file = tutorial/locale/tutorial.pot
-output_dir = tutorial/locale
-previous = true
-
diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py
index adfa70c9f..beeed75c9 100644
--- a/docs/tutorials/wiki/src/authorization/setup.py
+++ b/docs/tutorials/wiki/src/authorization/setup.py
@@ -3,30 +3,39 @@ import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
-README = open(os.path.join(here, 'README.txt')).read()
-CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
requires = [
'pyramid',
- 'repoze.zodbconn',
- 'repoze.tm2>=1.0b1', # default_commit_veto
- 'repoze.retry',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'pyramid_zodbconn',
+ 'transaction',
'ZODB3',
- 'WebError',
+ 'waitress',
'docutils',
]
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='tutorial',
version='0.0',
description='tutorial',
- long_description=README + '\n\n' + CHANGES,
+ long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Intended Audience :: Developers",
- "Framework :: Pylons",
- "Programming Language :: Python",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -34,13 +43,12 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
- tests_require=requires,
- test_suite="tutorial",
- entry_points = """\
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
""",
- paster_plugins=['pyramid'],
)
-
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index f7dab5f47..39b94abd1 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -1,31 +1,27 @@
-from repoze.zodbconn.finder import PersistentApplicationFinder
-
from pyramid.config import Configurator
+from pyramid_zodbconn import get_connection
+
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
-from tutorial.models import appmaker
-from tutorial.security import groupfinder
+from .models import appmaker
+from .security import groupfinder
+
+def root_factory(request):
+ conn = get_connection(request)
+ return appmaker(conn.root())
+
def main(global_config, **settings):
- """ This function returns a WSGI application.
-
- It is usually called by the PasteDeploy framework during
- ``paster serve``.
+ """ This function returns a Pyramid WSGI application.
"""
- authn_policy = AuthTktAuthenticationPolicy(secret='sosecret',
- callback=groupfinder)
+ authn_policy = AuthTktAuthenticationPolicy(
+ 'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
- 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)
- def get_root(request):
- return finder(request.environ)
- config = Configurator(root_factory=get_root, settings=settings,
- authentication_policy=authn_policy,
- authorization_policy=authz_policy)
- config.add_static_view('static', 'tutorial:static')
- config.scan('tutorial')
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
+ config.include('pyramid_chameleon')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/login.py b/docs/tutorials/wiki/src/authorization/tutorial/login.py
deleted file mode 100644
index 463db71a6..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/login.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from pyramid.httpexceptions import HTTPFound
-
-from pyramid.security import remember
-from pyramid.security import forget
-from pyramid.view import view_config
-from pyramid.url import resource_url
-
-from tutorial.security import USERS
-
-@view_config(context='tutorial.models.Wiki', name='login',
- renderer='templates/login.pt')
-@view_config(context='pyramid.exceptions.Forbidden',
- renderer='templates/login.pt')
-def login(request):
- login_url = resource_url(request.context, request, 'login')
- referrer = request.url
- if referrer == login_url:
- referrer = '/' # never use the login form itself as came_from
- came_from = request.params.get('came_from', referrer)
- message = ''
- login = ''
- password = ''
- if 'form.submitted' in request.params:
- login = request.params['login']
- password = request.params['password']
- if USERS.get(login) == password:
- headers = remember(request, login)
- return HTTPFound(location = came_from,
- headers = headers)
- message = 'Failed login'
-
- return dict(
- message = message,
- url = request.application_url + '/login',
- came_from = came_from,
- login = login,
- password = password,
- )
-
-@view_config(context='tutorial.models.Wiki', name='logout')
-def logout(request):
- headers = forget(request)
- return HTTPFound(location = resource_url(request.context, request),
- headers = headers)
-
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models.py
index 0a31c38be..38fdd2dfc 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/models.py
@@ -1,8 +1,10 @@
from persistent import Persistent
from persistent.mapping import PersistentMapping
-from pyramid.security import Allow
-from pyramid.security import Everyone
+from pyramid.security import (
+ Allow,
+ Everyone,
+ )
class Wiki(PersistentMapping):
__name__ = None
@@ -15,7 +17,7 @@ class Page(Persistent):
self.data = data
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = Wiki()
frontpage = Page('This is the front page')
app_root['FrontPage'] = frontpage
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/security.py b/docs/tutorials/wiki/src/authorization/tutorial/security.py
index cfd13071e..d88c9c71f 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/security.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/security.py
@@ -5,4 +5,3 @@ GROUPS = {'editor':['group:editors']}
def groupfinder(userid, request):
if userid in USERS:
return GROUPS.get(userid, [])
-
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/favicon.ico b/docs/tutorials/wiki/src/authorization/tutorial/static/favicon.ico
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/footerbg.png b/docs/tutorials/wiki/src/authorization/tutorial/static/footerbg.png
deleted file mode 100644
index 1fbc873da..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/footerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/headerbg.png b/docs/tutorials/wiki/src/authorization/tutorial/static/headerbg.png
deleted file mode 100644
index 0596f2020..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/headerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/ie6.css b/docs/tutorials/wiki/src/authorization/tutorial/static/ie6.css
deleted file mode 100644
index b7c8493d8..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/ie6.css
+++ /dev/null
@@ -1,8 +0,0 @@
-* html img,
-* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none",
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')",
-this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''),
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')",
-this.runtimeStyle.backgroundImage = "none")),this.pngSet=true)
-);}
-#wrap{display:table;height:100%}
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/middlebg.png b/docs/tutorials/wiki/src/authorization/tutorial/static/middlebg.png
deleted file mode 100644
index 2369cfb7d..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/middlebg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css b/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css
deleted file mode 100644
index fd1914d8d..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css
+++ /dev/null
@@ -1,65 +0,0 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
-/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-small.png b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-small.png
deleted file mode 100644
index a5bc0ade7..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-small.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png
index 347e05549..4ab837be9 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png
+++ b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/transparent.gif b/docs/tutorials/wiki/src/authorization/tutorial/static/transparent.gif
deleted file mode 100644
index 0341802e5..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/transparent.gif
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
index f9da6c414..823fa8972 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
@@ -1,62 +1,72 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon"
- href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/pylons.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/ie6.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top-small">
- <div class="top-small align-center">
- <div>
- <img width="220" height="50" alt="pyramid"
- src="${request.static_url('tutorial:static/pyramid-small.png')}" />
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>${page.__name__} - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <p tal:condition="logged_in" class="pull-right">
+ <a href="${request.application_url}/logout">Logout</a>
+ </p>
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ <form action="${save_url}" method="post">
+ <div class="form-group">
+ <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ </div>
+ <div class="form-group">
+ <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ </div>
+ </form>
+ </div>
+ </div>
</div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-right">
- <div id="left" class="app-welcome align-left">
- Editing <b><span tal:replace="page.__name__">Page Name
- Goes Here</span></b><br/>
- You can return to the
- <a href="${request.application_url}">FrontPage</a>.<br/>
- </div>
- <div id="right" class="app-welcome align-right">
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
</div>
</div>
- <div id="bottom">
- <div class="bottom">
- <form action="${save_url}" method="post">
- <textarea name="body" tal:content="page.data" rows="10"
- cols="60"/><br/>
- <input type="submit" name="form.submitted" value="Save"/>
- </form>
- </div>
- </div>
- </div>
- <div id="footer">
- <div class="footer"
- >&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
index 64e592ea9..4a938e9bb 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
@@ -1,58 +1,74 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>Login - Pyramid tutorial wiki (based on TurboGears
- 20-Minute Wiki)</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon"
- href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/pylons.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/ie6.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top-small">
- <div class="top-small align-center">
- <div>
- <img width="220" height="50" alt="pyramid"
- src="${request.static_url('tutorial:static/pyramid-small.png')}" />
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>Login - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <p>
+ <strong>
+ Login
+ </strong><br>
+ <span tal:replace="message"></span>
+ </p>
+ <form action="${url}" method="post">
+ <input type="hidden" name="came_from" value="${came_from}">
+ <div class="form-group">
+ <label for="login">Username</label>
+ <input type="text" name="login" value="${login}">
+ </div>
+ <div class="form-group">
+ <label for="password">Password</label>
+ <input type="password" name="password" value="${password}">
+ </div>
+ <div class="form-group">
+ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
+ </div>
+ </form>
+ </div>
+ </div>
</div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-right">
- <div id="left" class="app-welcome align-left">
- <b>Login</b><br/>
- <span tal:replace="message"/>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
- <div id="right" class="app-welcome align-right"></div>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <form action="${url}" method="post">
- <input type="hidden" name="came_from" value="${came_from}"/>
- <input type="text" name="login" value="${login}"/><br/>
- <input type="password" name="password"
- value="${password}"/><br/>
- <input type="submit" name="form.submitted" value="Log In"/>
- </form>
</div>
</div>
- </div>
- <div id="footer">
- <div class="footer"
- >&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
index d98420680..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
@@ -1,75 +1,67 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>The Pyramid Web Application Development Framework</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" />
- <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&amp;subset=latin" type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top">
- <div class="top align-center">
- <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-center">
- <p class="app-welcome">
- Welcome to <span class="app-name">${project}</span>, an application generated by<br/>
- the Pyramid web application development framework.
- </p>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <div id="left" class="align-right">
- <h2>Search documentation</h2>
- <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html">
- <input type="text" id="q" name="q" value="" />
- <input type="submit" id="x" value="Go" />
- </form>
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>ZODB Scaffold for The Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ </div>
+ </div>
</div>
- <div id="right" class="align-left">
- <h2>Pyramid links</h2>
- <ul class="links">
- <li>
- <a href="http://pylonsproject.org">Pylons Website</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a>
- </li>
- <li>
- <a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
- </li>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
</ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
</div>
</div>
- </div>
- <div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
index d207a0c23..fa35d758d 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
@@ -1,65 +1,72 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>${page.__name__} - Pyramid tutorial wiki (based on
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>${page.__name__} - Pyramid tutorial wiki (based on
TurboGears 20-Minute Wiki)</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon"
- href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/pylons.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/ie6.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top-small">
- <div class="top-small align-center">
- <div>
- <img width="220" height="50" alt="pyramid"
- src="${request.static_url('tutorial:static/pyramid-small.png')}" />
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <p tal:condition="logged_in" class="pull-right">
+ <a href="${request.application_url}/logout">Logout</a>
+ </p>
+ <div tal:replace="structure content">
+ Page text goes here.
+ </div>
+ <p>
+ <a tal:attributes="href edit_url" href="">
+ Edit this page
+ </a>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ </div>
+ </div>
</div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-right">
- <div id="left" class="app-welcome align-left">
- Viewing <b><span tal:replace="page.__name__">Page Name
- Goes Here</span></b><br/>
- You can return to the
- <a href="${request.application_url}">FrontPage</a>.<br/>
- </div>
- <div id="right" class="app-welcome align-right">
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
- </div>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <div tal:replace="structure content">
- Page text goes here.
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
- <p>
- <a tal:attributes="href edit_url" href="">
- Edit this page
- </a>
- </p>
</div>
</div>
- </div>
- <div id="footer">
- <div class="footer"
- >&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
index aaf753816..40f3c47af 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
@@ -2,123 +2,16 @@ import unittest
from pyramid import testing
-class PageModelTests(unittest.TestCase):
- def _getTargetClass(self):
- from tutorial.models import Page
- return Page
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
- def _makeOne(self, data=u'some data'):
- return self._getTargetClass()(data=data)
+ def tearDown(self):
+ testing.tearDown()
- def test_constructor(self):
- instance = self._makeOne()
- self.assertEqual(instance.data, u'some data')
-
-class WikiModelTests(unittest.TestCase):
-
- def _getTargetClass(self):
- from tutorial.models import Wiki
- return Wiki
-
- def _makeOne(self):
- return self._getTargetClass()()
-
- def test_it(self):
- wiki = self._makeOne()
- self.assertEqual(wiki.__parent__, None)
- self.assertEqual(wiki.__name__, None)
-
-class AppmakerTests(unittest.TestCase):
- def _callFUT(self, zodb_root):
- from tutorial.models import appmaker
- return appmaker(zodb_root)
-
- def test_it(self):
- root = {}
- self._callFUT(root)
- self.assertEqual(root['app_root']['FrontPage'].data,
- 'This is the front page')
-
-class ViewWikiTests(unittest.TestCase):
- def test_it(self):
- from tutorial.views import view_wiki
- context = testing.DummyResource()
- request = testing.DummyRequest()
- response = view_wiki(context, request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
-
-class ViewPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from tutorial.views import view_page
- return view_page(context, request)
-
- def test_it(self):
- wiki = testing.DummyResource()
- wiki['IDoExist'] = testing.DummyResource()
- context = testing.DummyResource(data='Hello CruelWorld IDoExist')
- context.__parent__ = wiki
- context.__name__ = 'thepage'
- request = testing.DummyRequest()
- info = self._callFUT(context, request)
- self.assertEqual(info['page'], context)
- 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/thepage/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from tutorial.views import add_page
- return add_page(context, request)
-
- def test_it_notsubmitted(self):
- from pyramid.url import resource_url
- context = testing.DummyResource()
- request = testing.DummyRequest()
- request.subpath = ['AnotherPage']
- info = self._callFUT(context, request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(info['save_url'],
- resource_url(
- context, request, 'add_page', 'AnotherPage'))
-
- def test_it_submitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.subpath = ['AnotherPage']
- self._callFUT(context, request)
- page = context['AnotherPage']
- self.assertEqual(page.data, 'Hello yo!')
- self.assertEqual(page.__name__, 'AnotherPage')
- self.assertEqual(page.__parent__, context)
-
-class EditPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from tutorial.views import edit_page
- return edit_page(context, request)
-
- def test_it_notsubmitted(self):
- from pyramid.url import resource_url
- context = testing.DummyResource()
+ def test_my_view(self):
+ from .views import my_view
request = testing.DummyRequest()
- info = self._callFUT(context, request)
- self.assertEqual(info['page'], context)
- self.assertEqual(info['save_url'],
- resource_url(context, request, 'edit_page'))
-
- def test_it_submitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- response = self._callFUT(context, request)
- self.assertEqual(response.location, 'http://example.com/')
- self.assertEqual(context.data, 'Hello yo!')
+ info = my_view(request)
+ self.assertEqual(info['project'], 'tutorial')
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py
index a83e17de4..c271d2cc1 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -2,21 +2,31 @@ from docutils.core import publish_parts
import re
from pyramid.httpexceptions import HTTPFound
-from pyramid.url import resource_url
-from pyramid.view import view_config
-from pyramid.security import authenticated_userid
-from tutorial.models import Page
+from pyramid.view import (
+ view_config,
+ forbidden_view_config,
+ )
+
+from pyramid.security import (
+ remember,
+ forget,
+ )
+
+
+from .security import USERS
+from .models import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='tutorial.models.Wiki', permission='view')
+@view_config(context='.models.Wiki',
+ permission='view')
def view_wiki(context, request):
- return HTTPFound(location=resource_url(context, request, 'FrontPage'))
+ return HTTPFound(location=request.resource_url(context, 'FrontPage'))
-@view_config(context='tutorial.models.Page',
- renderer='templates/view.pt', permission='view')
+@view_config(context='.models.Page', renderer='templates/view.pt',
+ permission='view')
def view_page(context, request):
wiki = context.__parent__
@@ -24,53 +34,83 @@ def view_page(context, request):
word = match.group(1)
if word in wiki:
page = wiki[word]
- view_url = resource_url(page, request)
+ view_url = request.resource_url(page)
return '<a href="%s">%s</a>' % (view_url, word)
else:
- add_url = request.application_url + '/add_page/' + word
+ add_url = request.application_url + '/add_page/' + word
return '<a href="%s">%s</a>' % (add_url, word)
content = publish_parts(context.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
- edit_url = resource_url(context, request, 'edit_page')
+ edit_url = request.resource_url(context, 'edit_page')
- logged_in = authenticated_userid(request)
+ return dict(page=context, content=content, edit_url=edit_url,
+ logged_in=request.authenticated_userid)
- return dict(page = context, content = content, edit_url = edit_url,
- logged_in = logged_in)
-
-@view_config(name='add_page', context='tutorial.models.Wiki',
+@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
permission='edit')
def add_page(context, request):
- name = request.subpath[0]
+ pagename = request.subpath[0]
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(body)
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- context[name] = page
- return HTTPFound(location = resource_url(page, request))
- save_url = resource_url(context, request, 'add_page', name)
+ context[pagename] = page
+ return HTTPFound(location=request.resource_url(page))
+ save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- logged_in = authenticated_userid(request)
-
- return dict(page = page, save_url = save_url, logged_in = logged_in)
+ return dict(page=page, save_url=save_url,
+ logged_in=request.authenticated_userid)
-@view_config(name='edit_page', context='tutorial.models.Page',
+@view_config(name='edit_page', context='.models.Page',
renderer='templates/edit.pt',
permission='edit')
def edit_page(context, request):
if 'form.submitted' in request.params:
context.data = request.params['body']
- return HTTPFound(location = resource_url(context, request))
+ return HTTPFound(location=request.resource_url(context))
+
+ return dict(page=context,
+ save_url=request.resource_url(context, 'edit_page'),
+ logged_in=request.authenticated_userid)
+
+@view_config(context='.models.Wiki', name='login',
+ renderer='templates/login.pt')
+@forbidden_view_config(renderer='templates/login.pt')
+def login(request):
+ login_url = request.resource_url(request.context, 'login')
+ referrer = request.url
+ if referrer == login_url:
+ referrer = '/' # never use the login form itself as came_from
+ came_from = request.params.get('came_from', referrer)
+ message = ''
+ login = ''
+ password = ''
+ if 'form.submitted' in request.params:
+ login = request.params['login']
+ password = request.params['password']
+ if USERS.get(login) == password:
+ headers = remember(request, login)
+ return HTTPFound(location=came_from,
+ headers=headers)
+ message = 'Failed login'
+
+ return dict(
+ message=message,
+ url=request.application_url + '/login',
+ came_from=came_from,
+ login=login,
+ password=password,
+ )
- logged_in = authenticated_userid(request)
- return dict(page = context,
- save_url = resource_url(context, request, 'edit_page'),
- logged_in = logged_in)
-
+@view_config(context='.models.Wiki', name='logout')
+def logout(request):
+ headers = forget(request)
+ return HTTPFound(location=request.resource_url(request.context),
+ headers=headers)
diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki/src/basiclayout/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini
index 555010bed..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/basiclayout/development.ini
+++ b/docs/tutorials/wiki/src/basiclayout/development.ini
@@ -1,34 +1,44 @@
-[app:tutorial]
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
use = egg:tutorial
-reload_templates = true
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = true
-default_locale_name = en
-zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
-
-[pipeline:main]
-pipeline =
- egg:WebError#evalerror
- egg:repoze.zodbconn#closer
- egg:repoze.retry#retry
- tm
- tutorial
-
-[filter:tm]
-use = egg:repoze.tm2#tm
-commit_veto = repoze.tm:default_commit_veto
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_zodbconn
+ pyramid_tm
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
[server:main]
-use = egg:Paste#http
-host = 0.0.0.0
+use = egg:waitress#main
+host = 127.0.0.1
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -40,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -47,6 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini
index 5c47ade9b..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/basiclayout/production.ini
+++ b/docs/tutorials/wiki/src/basiclayout/production.ini
@@ -1,45 +1,36 @@
-[app:tutorial]
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
use = egg:tutorial
-reload_templates = false
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = false
-default_locale_name = en
-zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
-[filter:weberror]
-use = egg:WebError#error_catcher
-debug = false
-;error_log =
-;show_exceptions_in_wsgi_errors = true
-;smtp_server = localhost
-;error_email = janitor@example.com
-;smtp_username = janitor
-;smtp_password = "janitor's password"
-;from_address = paste@localhost
-;error_subject_prefix = "Pyramid Error"
-;smtp_use_tls =
-;error_message =
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_tm
+ pyramid_zodbconn
-[filter:tm]
-use = egg:repoze.tm2#tm
-commit_veto = repoze.tm:default_commit_veto
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
-[pipeline:main]
-pipeline =
- weberror
- egg:repoze.zodbconn#closer
- egg:repoze.retry#retry
- tm
- tutorial
+###
+# wsgi server configuration
+###
[server:main]
-use = egg:Paste#http
+use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -66,6 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.cfg b/docs/tutorials/wiki/src/basiclayout/setup.cfg
deleted file mode 100644
index 23b2ad983..000000000
--- a/docs/tutorials/wiki/src/basiclayout/setup.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
-[nosetests]
-match=^test
-nocapture=1
-cover-package=tutorial
-with-coverage=1
-cover-erase=1
-
-[compile_catalog]
-directory = tutorial/locale
-domain = tutorial
-statistics = true
-
-[extract_messages]
-add_comments = TRANSLATORS:
-output_file = tutorial/locale/tutorial.pot
-width = 80
-
-[init_catalog]
-domain = tutorial
-input_file = tutorial/locale/tutorial.pot
-output_dir = tutorial/locale
-
-[update_catalog]
-domain = tutorial
-input_file = tutorial/locale/tutorial.pot
-output_dir = tutorial/locale
-previous = true
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py
index 2d540d65b..46b395568 100644
--- a/docs/tutorials/wiki/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki/src/basiclayout/setup.py
@@ -3,28 +3,38 @@ import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
-README = open(os.path.join(here, 'README.txt')).read()
-CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
requires = [
'pyramid',
- 'repoze.zodbconn',
- 'repoze.tm2>=1.0b1', # default_commit_veto
- 'repoze.retry',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'pyramid_zodbconn',
+ 'transaction',
'ZODB3',
- 'WebError',
+ 'waitress',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
]
setup(name='tutorial',
version='0.0',
description='tutorial',
- long_description=README + '\n\n' + CHANGES,
+ long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pylons",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -32,13 +42,12 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- install_requires = requires,
- tests_require= requires,
- test_suite="tutorial",
- entry_points = """\
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
""",
- paster_plugins=['pyramid'],
)
-
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index 6a4093a3b..f2a86df47 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -1,18 +1,18 @@
from pyramid.config import Configurator
-from repoze.zodbconn.finder import PersistentApplicationFinder
-from tutorial.models import appmaker
+from pyramid_zodbconn import get_connection
+from .models import appmaker
+
+
+def root_factory(request):
+ conn = get_connection(request)
+ return appmaker(conn.root())
+
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- 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)
- def get_root(request):
- return finder(request.environ)
- config = Configurator(root_factory=get_root, settings=settings)
- config.add_static_view('static', 'tutorial:static')
- config.scan('tutorial')
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
index 8dd0f5a49..e5aa3e9f7 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
@@ -1,10 +1,12 @@
from persistent.mapping import PersistentMapping
+
class MyModel(PersistentMapping):
__parent__ = __name__ = None
+
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = MyModel()
zodb_root['app_root'] = app_root
import transaction
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/favicon.ico b/docs/tutorials/wiki/src/basiclayout/tutorial/static/favicon.ico
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/footerbg.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/footerbg.png
deleted file mode 100644
index 1fbc873da..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/footerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/headerbg.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/headerbg.png
deleted file mode 100644
index 0596f2020..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/headerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/ie6.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/ie6.css
deleted file mode 100644
index b7c8493d8..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/ie6.css
+++ /dev/null
@@ -1,8 +0,0 @@
-* html img,
-* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none",
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')",
-this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''),
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')",
-this.runtimeStyle.backgroundImage = "none")),this.pngSet=true)
-);}
-#wrap{display:table;height:100%}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/middlebg.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/middlebg.png
deleted file mode 100644
index 2369cfb7d..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/middlebg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css
deleted file mode 100644
index fd1914d8d..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css
+++ /dev/null
@@ -1,65 +0,0 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
-/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-small.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-small.png
deleted file mode 100644
index a5bc0ade7..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-small.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png
index 347e05549..4ab837be9 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/transparent.gif b/docs/tutorials/wiki/src/basiclayout/tutorial/static/transparent.gif
deleted file mode 100644
index 0341802e5..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/transparent.gif
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
index c24daa711..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
@@ -1,75 +1,67 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>The Pyramid Web Application Development Framework</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" />
- <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&amp;subset=latin" type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top">
- <div class="top align-center">
- <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-center">
- <p class="app-welcome">
- Welcome to <span class="app-name">${project}</span>, an application generated by<br/>
- the Pyramid web application development framework.
- </p>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <div id="left" class="align-right">
- <h2>Search documentation</h2>
- <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html">
- <input type="text" id="q" name="q" value="" />
- <input type="submit" id="x" value="Go" />
- </form>
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>ZODB Scaffold for The Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ </div>
+ </div>
</div>
- <div id="right" class="align-left">
- <h2>Pyramid links</h2>
- <ul class="links">
- <li>
- <a href="http://pylonsproject.org/">Pylons Website</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a>
- </li>
- <li>
- <a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
- </li>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
</ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
</div>
</div>
- </div>
- <div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
index 1f3c3bb4d..40f3c47af 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
@@ -2,6 +2,7 @@ import unittest
from pyramid import testing
+
class ViewTests(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
@@ -10,8 +11,7 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from tutorial.views import my_view
+ from .views import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'tutorial')
-
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
index 157b9ac8f..628ce15ed 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
@@ -1,7 +1,7 @@
from pyramid.view import view_config
-from tutorial.models import MyModel
+from .models import MyModel
-@view_config(context=MyModel,
- renderer='tutorial:templates/mytemplate.pt')
+
+@view_config(context=MyModel, renderer='templates/mytemplate.pt')
def my_view(request):
- return {'project':'tutorial'}
+ return {'project': 'tutorial'}
diff --git a/docs/tutorials/wiki/src/installation/CHANGES.txt b/docs/tutorials/wiki/src/installation/CHANGES.txt
new file mode 100644
index 000000000..35a34f332
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version
diff --git a/docs/tutorials/wiki/src/installation/MANIFEST.in b/docs/tutorials/wiki/src/installation/MANIFEST.in
new file mode 100644
index 000000000..81beba1b1
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.txt *.ini *.cfg *.rst
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt
new file mode 100644
index 000000000..dcb3605b8
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/README.txt
@@ -0,0 +1,12 @@
+tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki/src/installation/development.ini b/docs/tutorials/wiki/src/installation/development.ini
new file mode 100644
index 000000000..6bf4b198e
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/development.ini
@@ -0,0 +1,65 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_zodbconn
+ pyramid_tm
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+host = 127.0.0.1
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/installation/production.ini b/docs/tutorials/wiki/src/installation/production.ini
new file mode 100644
index 000000000..4e9892e7b
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/production.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_tm
+ pyramid_zodbconn
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+host = 0.0.0.0
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_tutorial]
+level = WARN
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py
new file mode 100644
index 000000000..46b395568
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/setup.py
@@ -0,0 +1,53 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'pyramid_zodbconn',
+ 'transaction',
+ 'ZODB3',
+ 'waitress',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
+setup(name='tutorial',
+ version='0.0',
+ description='tutorial',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pylons pyramid',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+ )
diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
new file mode 100644
index 000000000..f2a86df47
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
@@ -0,0 +1,18 @@
+from pyramid.config import Configurator
+from pyramid_zodbconn import get_connection
+from .models import appmaker
+
+
+def root_factory(request):
+ conn = get_connection(request)
+ return appmaker(conn.root())
+
+
+def main(global_config, **settings):
+ """ This function returns a Pyramid WSGI application.
+ """
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/installation/tutorial/models.py b/docs/tutorials/wiki/src/installation/tutorial/models.py
new file mode 100644
index 000000000..e5aa3e9f7
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/models.py
@@ -0,0 +1,14 @@
+from persistent.mapping import PersistentMapping
+
+
+class MyModel(PersistentMapping):
+ __parent__ = __name__ = None
+
+
+def appmaker(zodb_root):
+ if 'app_root' not in zodb_root:
+ app_root = MyModel()
+ zodb_root['app_root'] = app_root
+ import transaction
+ transaction.commit()
+ return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki/src/installation/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/installation/tutorial/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
new file mode 100644
index 000000000..f8cbe2e2c
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>ZODB Scaffold for The Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/tests.py b/docs/tutorials/wiki/src/installation/tutorial/tests.py
new file mode 100644
index 000000000..40f3c47af
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/tests.py
@@ -0,0 +1,17 @@
+import unittest
+
+from pyramid import testing
+
+
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_my_view(self):
+ from .views import my_view
+ request = testing.DummyRequest()
+ info = my_view(request)
+ self.assertEqual(info['project'], 'tutorial')
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views.py b/docs/tutorials/wiki/src/installation/tutorial/views.py
new file mode 100644
index 000000000..628ce15ed
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/views.py
@@ -0,0 +1,7 @@
+from pyramid.view import view_config
+from .models import MyModel
+
+
+@view_config(context=MyModel, renderer='templates/mytemplate.pt')
+def my_view(request):
+ return {'project': 'tutorial'}
diff --git a/docs/tutorials/wiki/src/models/CHANGES.txt b/docs/tutorials/wiki/src/models/CHANGES.txt
index ffa255da8..35a34f332 100644
--- a/docs/tutorials/wiki/src/models/CHANGES.txt
+++ b/docs/tutorials/wiki/src/models/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version
diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/models/README.txt
+++ b/docs/tutorials/wiki/src/models/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini
index 1ba746d0e..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/models/development.ini
+++ b/docs/tutorials/wiki/src/models/development.ini
@@ -1,34 +1,44 @@
-[app:tutorial]
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
use = egg:tutorial
-reload_templates = true
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = true
-default_locale_name = en
-zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
-
-[pipeline:main]
-pipeline =
- egg:WebError#evalerror
- egg:repoze.zodbconn#closer
- egg:repoze.retry#retry
- tm
- tutorial
-
-[filter:tm]
-use = egg:repoze.tm2#tm
-commit_veto = repoze.tm:default_commit_veto
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_zodbconn
+ pyramid_tm
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
[server:main]
-use = egg:Paste#http
-host = 0.0.0.0
+use = egg:waitress#main
+host = 127.0.0.1
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -40,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -47,6 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini
index 5c47ade9b..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/models/production.ini
+++ b/docs/tutorials/wiki/src/models/production.ini
@@ -1,45 +1,36 @@
-[app:tutorial]
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
use = egg:tutorial
-reload_templates = false
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = false
-default_locale_name = en
-zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
-[filter:weberror]
-use = egg:WebError#error_catcher
-debug = false
-;error_log =
-;show_exceptions_in_wsgi_errors = true
-;smtp_server = localhost
-;error_email = janitor@example.com
-;smtp_username = janitor
-;smtp_password = "janitor's password"
-;from_address = paste@localhost
-;error_subject_prefix = "Pyramid Error"
-;smtp_use_tls =
-;error_message =
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_tm
+ pyramid_zodbconn
-[filter:tm]
-use = egg:repoze.tm2#tm
-commit_veto = repoze.tm:default_commit_veto
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
-[pipeline:main]
-pipeline =
- weberror
- egg:repoze.zodbconn#closer
- egg:repoze.retry#retry
- tm
- tutorial
+###
+# wsgi server configuration
+###
[server:main]
-use = egg:Paste#http
+use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -66,6 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/models/setup.cfg b/docs/tutorials/wiki/src/models/setup.cfg
deleted file mode 100644
index 3d7ea6e23..000000000
--- a/docs/tutorials/wiki/src/models/setup.cfg
+++ /dev/null
@@ -1,28 +0,0 @@
-[nosetests]
-match=^test
-nocapture=1
-cover-package=tutorial
-with-coverage=1
-cover-erase=1
-
-[compile_catalog]
-directory = tutorial/locale
-domain = tutorial
-statistics = true
-
-[extract_messages]
-add_comments = TRANSLATORS:
-output_file = tutorial/locale/tutorial.pot
-width = 80
-
-[init_catalog]
-domain = tutorial
-input_file = tutorial/locale/tutorial.pot
-output_dir = tutorial/locale
-
-[update_catalog]
-domain = tutorial
-input_file = tutorial/locale/tutorial.pot
-output_dir = tutorial/locale
-previous = true
-
diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py
index 2d540d65b..46b395568 100644
--- a/docs/tutorials/wiki/src/models/setup.py
+++ b/docs/tutorials/wiki/src/models/setup.py
@@ -3,28 +3,38 @@ import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
-README = open(os.path.join(here, 'README.txt')).read()
-CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
requires = [
'pyramid',
- 'repoze.zodbconn',
- 'repoze.tm2>=1.0b1', # default_commit_veto
- 'repoze.retry',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'pyramid_zodbconn',
+ 'transaction',
'ZODB3',
- 'WebError',
+ 'waitress',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
]
setup(name='tutorial',
version='0.0',
description='tutorial',
- long_description=README + '\n\n' + CHANGES,
+ long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pylons",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -32,13 +42,12 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- install_requires = requires,
- tests_require= requires,
- test_suite="tutorial",
- entry_points = """\
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
""",
- paster_plugins=['pyramid'],
)
-
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index 73fc81d23..f2a86df47 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -1,19 +1,18 @@
from pyramid.config import Configurator
-from repoze.zodbconn.finder import PersistentApplicationFinder
-from tutorial.models import appmaker
+from pyramid_zodbconn import get_connection
+from .models import appmaker
+
+
+def root_factory(request):
+ conn = get_connection(request)
+ return appmaker(conn.root())
+
def main(global_config, **settings):
- """ This function returns a WSGI application.
+ """ This function returns a Pyramid WSGI application.
"""
- 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)
- def get_root(request):
- return finder(request.environ)
- config = Configurator(root_factory=get_root, settings=settings)
- config.add_static_view('static', 'tutorial:static')
- config.scan('tutorial')
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
return config.make_wsgi_app()
-
diff --git a/docs/tutorials/wiki/src/models/tutorial/models.py b/docs/tutorials/wiki/src/models/tutorial/models.py
index 9761856c6..aa907aee5 100644
--- a/docs/tutorials/wiki/src/models/tutorial/models.py
+++ b/docs/tutorials/wiki/src/models/tutorial/models.py
@@ -10,7 +10,7 @@ class Page(Persistent):
self.data = data
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = Wiki()
frontpage = Page('This is the front page')
app_root['FrontPage'] = frontpage
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/favicon.ico b/docs/tutorials/wiki/src/models/tutorial/static/favicon.ico
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/footerbg.png b/docs/tutorials/wiki/src/models/tutorial/static/footerbg.png
deleted file mode 100644
index 1fbc873da..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/footerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/headerbg.png b/docs/tutorials/wiki/src/models/tutorial/static/headerbg.png
deleted file mode 100644
index 0596f2020..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/headerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/ie6.css b/docs/tutorials/wiki/src/models/tutorial/static/ie6.css
deleted file mode 100644
index b7c8493d8..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/ie6.css
+++ /dev/null
@@ -1,8 +0,0 @@
-* html img,
-* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none",
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')",
-this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''),
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')",
-this.runtimeStyle.backgroundImage = "none")),this.pngSet=true)
-);}
-#wrap{display:table;height:100%}
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/middlebg.png b/docs/tutorials/wiki/src/models/tutorial/static/middlebg.png
deleted file mode 100644
index 2369cfb7d..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/middlebg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pylons.css b/docs/tutorials/wiki/src/models/tutorial/static/pylons.css
deleted file mode 100644
index a9f49cc85..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/pylons.css
+++ /dev/null
@@ -1,65 +0,0 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-snall,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
-/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki/src/models/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pyramid-small.png b/docs/tutorials/wiki/src/models/tutorial/static/pyramid-small.png
deleted file mode 100644
index a5bc0ade7..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/pyramid-small.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/models/tutorial/static/pyramid.png
index 347e05549..4ab837be9 100644
--- a/docs/tutorials/wiki/src/models/tutorial/static/pyramid.png
+++ b/docs/tutorials/wiki/src/models/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/theme.css b/docs/tutorials/wiki/src/models/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/transparent.gif b/docs/tutorials/wiki/src/models/tutorial/static/transparent.gif
deleted file mode 100644
index 0341802e5..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/transparent.gif
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
index d98420680..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
@@ -1,75 +1,67 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>The Pyramid Web Application Development Framework</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" />
- <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&amp;subset=latin" type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top">
- <div class="top align-center">
- <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-center">
- <p class="app-welcome">
- Welcome to <span class="app-name">${project}</span>, an application generated by<br/>
- the Pyramid web application development framework.
- </p>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <div id="left" class="align-right">
- <h2>Search documentation</h2>
- <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html">
- <input type="text" id="q" name="q" value="" />
- <input type="submit" id="x" value="Go" />
- </form>
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>ZODB Scaffold for The Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ </div>
+ </div>
</div>
- <div id="right" class="align-left">
- <h2>Pyramid links</h2>
- <ul class="links">
- <li>
- <a href="http://pylonsproject.org">Pylons Website</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a>
- </li>
- <li>
- <a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
- </li>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
</ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
</div>
</div>
- </div>
- <div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py
index 51c97a95d..40f3c47af 100644
--- a/docs/tutorials/wiki/src/models/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/models/tutorial/tests.py
@@ -2,50 +2,6 @@ import unittest
from pyramid import testing
-class PageModelTests(unittest.TestCase):
-
- def _getTargetClass(self):
- from tutorial.models import Page
- return Page
-
- def _makeOne(self, data=u'some data'):
- return self._getTargetClass()(data=data)
-
- def test_constructor(self):
- instance = self._makeOne()
- self.assertEqual(instance.data, u'some data')
-
-class WikiModelTests(unittest.TestCase):
-
- def _getTargetClass(self):
- from tutorial.models import Wiki
- return Wiki
-
- def _makeOne(self):
- return self._getTargetClass()()
-
- def test_it(self):
- wiki = self._makeOne()
- self.assertEqual(wiki.__parent__, None)
- self.assertEqual(wiki.__name__, None)
-
-class AppmakerTests(unittest.TestCase):
-
- def _callFUT(self, zodb_root):
- from tutorial.models import appmaker
- return appmaker(zodb_root)
-
- def test_no_app_root(self):
- root = {}
- self._callFUT(root)
- self.assertEqual(root['app_root']['FrontPage'].data,
- 'This is the front page')
-
- def test_w_app_root(self):
- app_root = object()
- root = {'app_root': app_root}
- self._callFUT(root)
- self.failUnless(root['app_root'] is app_root)
class ViewTests(unittest.TestCase):
def setUp(self):
@@ -55,7 +11,7 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from tutorial.views import my_view
+ from .views import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'tutorial')
diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py
index 2346602c9..628ce15ed 100644
--- a/docs/tutorials/wiki/src/models/tutorial/views.py
+++ b/docs/tutorials/wiki/src/models/tutorial/views.py
@@ -1,5 +1,7 @@
from pyramid.view import view_config
+from .models import MyModel
-@view_config(renderer='tutorial:templates/mytemplate.pt')
+
+@view_config(context=MyModel, renderer='templates/mytemplate.pt')
def my_view(request):
- return {'project':'tutorial'}
+ return {'project': 'tutorial'}
diff --git a/docs/tutorials/wiki/src/tests/CHANGES.txt b/docs/tutorials/wiki/src/tests/CHANGES.txt
new file mode 100644
index 000000000..35a34f332
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version
diff --git a/docs/tutorials/wiki/src/tests/MANIFEST.in b/docs/tutorials/wiki/src/tests/MANIFEST.in
new file mode 100644
index 000000000..81beba1b1
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.txt *.ini *.cfg *.rst
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki/src/tests/README.txt b/docs/tutorials/wiki/src/tests/README.txt
new file mode 100644
index 000000000..dcb3605b8
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/README.txt
@@ -0,0 +1,12 @@
+tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini
new file mode 100644
index 000000000..6bf4b198e
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/development.ini
@@ -0,0 +1,65 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_zodbconn
+ pyramid_tm
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+host = 127.0.0.1
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini
new file mode 100644
index 000000000..4e9892e7b
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/production.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_tm
+ pyramid_zodbconn
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+host = 0.0.0.0
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_tutorial]
+level = WARN
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py
new file mode 100644
index 000000000..beeed75c9
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/setup.py
@@ -0,0 +1,54 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'pyramid_zodbconn',
+ 'transaction',
+ 'ZODB3',
+ 'waitress',
+ 'docutils',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
+setup(name='tutorial',
+ version='0.0',
+ description='tutorial',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pylons pyramid',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+ )
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
new file mode 100644
index 000000000..39b94abd1
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -0,0 +1,27 @@
+from pyramid.config import Configurator
+from pyramid_zodbconn import get_connection
+
+from pyramid.authentication import AuthTktAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+
+from .models import appmaker
+from .security import groupfinder
+
+def root_factory(request):
+ conn = get_connection(request)
+ return appmaker(conn.root())
+
+
+def main(global_config, **settings):
+ """ This function returns a Pyramid WSGI application.
+ """
+ authn_policy = AuthTktAuthenticationPolicy(
+ 'sosecret', callback=groupfinder, hashalg='sha512')
+ authz_policy = ACLAuthorizationPolicy()
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(authz_policy)
+ config.include('pyramid_chameleon')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models.py
new file mode 100644
index 000000000..38fdd2dfc
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/models.py
@@ -0,0 +1,29 @@
+from persistent import Persistent
+from persistent.mapping import PersistentMapping
+
+from pyramid.security import (
+ Allow,
+ Everyone,
+ )
+
+class Wiki(PersistentMapping):
+ __name__ = None
+ __parent__ = None
+ __acl__ = [ (Allow, Everyone, 'view'),
+ (Allow, 'group:editors', 'edit') ]
+
+class Page(Persistent):
+ def __init__(self, data):
+ self.data = data
+
+def appmaker(zodb_root):
+ if 'app_root' not in zodb_root:
+ app_root = Wiki()
+ frontpage = Page('This is the front page')
+ app_root['FrontPage'] = frontpage
+ frontpage.__name__ = 'FrontPage'
+ frontpage.__parent__ = app_root
+ zodb_root['app_root'] = app_root
+ import transaction
+ transaction.commit()
+ return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/tests/tutorial/security.py b/docs/tutorials/wiki/src/tests/tutorial/security.py
new file mode 100644
index 000000000..d88c9c71f
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/security.py
@@ -0,0 +1,7 @@
+USERS = {'editor':'editor',
+ 'viewer':'viewer'}
+GROUPS = {'editor':['group:editors']}
+
+def groupfinder(userid, request):
+ if userid in USERS:
+ return GROUPS.get(userid, [])
diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki/src/tests/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/tests/tutorial/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
new file mode 100644
index 000000000..823fa8972
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>${page.__name__} - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <p tal:condition="logged_in" class="pull-right">
+ <a href="${request.application_url}/logout">Logout</a>
+ </p>
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ <form action="${save_url}" method="post">
+ <div class="form-group">
+ <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ </div>
+ <div class="form-group">
+ <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
new file mode 100644
index 000000000..4a938e9bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>Login - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <p>
+ <strong>
+ Login
+ </strong><br>
+ <span tal:replace="message"></span>
+ </p>
+ <form action="${url}" method="post">
+ <input type="hidden" name="came_from" value="${came_from}">
+ <div class="form-group">
+ <label for="login">Username</label>
+ <input type="text" name="login" value="${login}">
+ </div>
+ <div class="form-group">
+ <label for="password">Password</label>
+ <input type="password" name="password" value="${password}">
+ </div>
+ <div class="form-group">
+ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
new file mode 100644
index 000000000..f8cbe2e2c
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>ZODB Scaffold for The Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
new file mode 100644
index 000000000..fa35d758d
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>${page.__name__} - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <p tal:condition="logged_in" class="pull-right">
+ <a href="${request.application_url}/logout">Logout</a>
+ </p>
+ <div tal:replace="structure content">
+ Page text goes here.
+ </div>
+ <p>
+ <a tal:attributes="href edit_url" href="">
+ Edit this page
+ </a>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py
index 0ce5ea718..04beaea44 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py
@@ -5,7 +5,7 @@ from pyramid import testing
class PageModelTests(unittest.TestCase):
def _getTargetClass(self):
- from tutorial.models import Page
+ from .models import Page
return Page
def _makeOne(self, data=u'some data'):
@@ -18,7 +18,7 @@ class PageModelTests(unittest.TestCase):
class WikiModelTests(unittest.TestCase):
def _getTargetClass(self):
- from tutorial.models import Wiki
+ from .models import Wiki
return Wiki
def _makeOne(self):
@@ -30,8 +30,9 @@ class WikiModelTests(unittest.TestCase):
self.assertEqual(wiki.__name__, None)
class AppmakerTests(unittest.TestCase):
+
def _callFUT(self, zodb_root):
- from tutorial.models import appmaker
+ from .models import appmaker
return appmaker(zodb_root)
def test_it(self):
@@ -42,7 +43,7 @@ class AppmakerTests(unittest.TestCase):
class ViewWikiTests(unittest.TestCase):
def test_it(self):
- from tutorial.views import view_wiki
+ from .views import view_wiki
context = testing.DummyResource()
request = testing.DummyRequest()
response = view_wiki(context, request)
@@ -50,7 +51,7 @@ class ViewWikiTests(unittest.TestCase):
class ViewPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from tutorial.views import view_page
+ from .views import view_page
return view_page(context, request)
def test_it(self):
@@ -76,11 +77,10 @@ class ViewPageTests(unittest.TestCase):
class AddPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from tutorial.views import add_page
+ from .views import add_page
return add_page(context, request)
def test_it_notsubmitted(self):
- from pyramid.url import resource_url
context = testing.DummyResource()
request = testing.DummyRequest()
request.subpath = ['AnotherPage']
@@ -88,7 +88,7 @@ class AddPageTests(unittest.TestCase):
self.assertEqual(info['page'].data,'')
self.assertEqual(
info['save_url'],
- resource_url(context, request, 'add_page', 'AnotherPage'))
+ request.resource_url(context, 'add_page', 'AnotherPage'))
def test_it_submitted(self):
context = testing.DummyResource()
@@ -103,17 +103,16 @@ class AddPageTests(unittest.TestCase):
class EditPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from tutorial.views import edit_page
+ from .views import edit_page
return edit_page(context, request)
def test_it_notsubmitted(self):
- from pyramid.url import resource_url
context = testing.DummyResource()
request = testing.DummyRequest()
info = self._callFUT(context, request)
self.assertEqual(info['page'], context)
self.assertEqual(info['save_url'],
- resource_url(context, request, 'edit_page'))
+ request.resource_url(context, 'edit_page'))
def test_it_submitted(self):
context = testing.DummyResource()
@@ -135,18 +134,16 @@ class FunctionalTests(unittest.TestCase):
def setUp(self):
import tempfile
import os.path
- from tutorial import main
+ from . import main
self.tmpdir = tempfile.mkdtemp()
dbpath = os.path.join( self.tmpdir, 'test.db')
- from repoze.zodbconn.uri import db_from_uri
- db = db_from_uri('file://' + dbpath)
- settings = { 'zodb_uri' : None }
+ uri = 'file://' + dbpath
+ settings = { 'zodbconn.uri' : uri ,
+ 'pyramid.includes': ['pyramid_zodbconn', 'pyramid_tm'] }
app = main({}, **settings)
- from repoze.zodbconn.connector import Connector
- app = Connector(app, db)
- self.db = db
+ self.db = app.registry._zodb_databases['']
from webtest import TestApp
self.testapp = TestApp(app)
@@ -157,64 +154,68 @@ class FunctionalTests(unittest.TestCase):
def test_root(self):
res = self.testapp.get('/', status=302)
- self.assertTrue(not res.body)
+ self.assertEqual(res.location, 'http://localhost/FrontPage')
def test_FrontPage(self):
res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue('FrontPage' in res.body)
+ self.assertTrue(b'FrontPage' in res.body)
def test_unexisting_page(self):
res = self.testapp.get('/SomePage', status=404)
- self.assertTrue('Not Found' in res.body)
+ self.assertTrue(b'Not Found' in res.body)
+
+ def test_referrer_is_login(self):
+ res = self.testapp.get('/login', status=200)
+ self.assertTrue(b'name="came_from" value="/"' in res.body)
def test_successful_log_in(self):
res = self.testapp.get( self.viewer_login, status=302)
- self.assertTrue(res.location == 'FrontPage')
+ self.assertEqual(res.location, 'http://localhost/FrontPage')
def test_failed_log_in(self):
res = self.testapp.get( self.viewer_wrong_login, status=200)
- self.assertTrue('login' in res.body)
+ self.assertTrue(b'login' in res.body)
def test_logout_link_present_when_logged_in(self):
res = self.testapp.get( self.viewer_login, status=302)
res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue('Logout' in res.body)
+ self.assertTrue(b'Logout' in res.body)
def test_logout_link_not_present_after_logged_out(self):
res = self.testapp.get( self.viewer_login, status=302)
res = self.testapp.get('/FrontPage', status=200)
res = self.testapp.get('/logout', status=302)
- self.assertTrue('Logout' not in res.body)
+ self.assertTrue(b'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)
+ self.assertTrue(b'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)
+ self.assertTrue(b'Login' in res.body)
def test_viewer_user_cannot_edit(self):
res = self.testapp.get( self.viewer_login, status=302)
res = self.testapp.get('/FrontPage/edit_page', status=200)
- self.assertTrue('Login' in res.body)
+ self.assertTrue(b'Login' in res.body)
def test_viewer_user_cannot_add(self):
res = self.testapp.get( self.viewer_login, status=302)
res = self.testapp.get('/add_page/NewPage', status=200)
- self.assertTrue('Login' in res.body)
+ self.assertTrue(b'Login' in res.body)
def test_editors_member_user_can_edit(self):
res = self.testapp.get( self.editor_login, status=302)
res = self.testapp.get('/FrontPage/edit_page', status=200)
- self.assertTrue('Editing' in res.body)
+ self.assertTrue(b'Editing' in res.body)
def test_editors_member_user_can_add(self):
res = self.testapp.get( self.editor_login, status=302)
res = self.testapp.get('/add_page/NewPage', status=200)
- self.assertTrue('Editing' in res.body)
+ self.assertTrue(b'Editing' in res.body)
def test_editors_member_user_can_view(self):
res = self.testapp.get( self.editor_login, status=302)
res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue('FrontPage' in res.body)
+ self.assertTrue(b'FrontPage' in res.body)
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py
new file mode 100644
index 000000000..c271d2cc1
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/views.py
@@ -0,0 +1,116 @@
+from docutils.core import publish_parts
+import re
+
+from pyramid.httpexceptions import HTTPFound
+
+from pyramid.view import (
+ view_config,
+ forbidden_view_config,
+ )
+
+from pyramid.security import (
+ remember,
+ forget,
+ )
+
+
+from .security import USERS
+from .models import Page
+
+# regular expression used to find WikiWords
+wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
+
+@view_config(context='.models.Wiki',
+ permission='view')
+def view_wiki(context, request):
+ return HTTPFound(location=request.resource_url(context, 'FrontPage'))
+
+@view_config(context='.models.Page', renderer='templates/view.pt',
+ permission='view')
+def view_page(context, request):
+ wiki = context.__parent__
+
+ def check(match):
+ word = match.group(1)
+ if word in wiki:
+ page = wiki[word]
+ view_url = request.resource_url(page)
+ return '<a href="%s">%s</a>' % (view_url, word)
+ else:
+ add_url = request.application_url + '/add_page/' + word
+ return '<a href="%s">%s</a>' % (add_url, word)
+
+ content = publish_parts(context.data, writer_name='html')['html_body']
+ content = wikiwords.sub(check, content)
+ edit_url = request.resource_url(context, 'edit_page')
+
+ return dict(page=context, content=content, edit_url=edit_url,
+ logged_in=request.authenticated_userid)
+
+@view_config(name='add_page', context='.models.Wiki',
+ renderer='templates/edit.pt',
+ permission='edit')
+def add_page(context, request):
+ pagename = request.subpath[0]
+ if 'form.submitted' in request.params:
+ body = request.params['body']
+ page = Page(body)
+ page.__name__ = pagename
+ page.__parent__ = context
+ context[pagename] = page
+ return HTTPFound(location=request.resource_url(page))
+ save_url = request.resource_url(context, 'add_page', pagename)
+ page = Page('')
+ page.__name__ = pagename
+ page.__parent__ = context
+
+ return dict(page=page, save_url=save_url,
+ logged_in=request.authenticated_userid)
+
+@view_config(name='edit_page', context='.models.Page',
+ renderer='templates/edit.pt',
+ permission='edit')
+def edit_page(context, request):
+ if 'form.submitted' in request.params:
+ context.data = request.params['body']
+ return HTTPFound(location=request.resource_url(context))
+
+ return dict(page=context,
+ save_url=request.resource_url(context, 'edit_page'),
+ logged_in=request.authenticated_userid)
+
+@view_config(context='.models.Wiki', name='login',
+ renderer='templates/login.pt')
+@forbidden_view_config(renderer='templates/login.pt')
+def login(request):
+ login_url = request.resource_url(request.context, 'login')
+ referrer = request.url
+ if referrer == login_url:
+ referrer = '/' # never use the login form itself as came_from
+ came_from = request.params.get('came_from', referrer)
+ message = ''
+ login = ''
+ password = ''
+ if 'form.submitted' in request.params:
+ login = request.params['login']
+ password = request.params['password']
+ if USERS.get(login) == password:
+ headers = remember(request, login)
+ return HTTPFound(location=came_from,
+ headers=headers)
+ message = 'Failed login'
+
+ return dict(
+ message=message,
+ url=request.application_url + '/login',
+ came_from=came_from,
+ login=login,
+ password=password,
+ )
+
+
+@view_config(context='.models.Wiki', name='logout')
+def logout(request):
+ headers = forget(request)
+ return HTTPFound(location=request.resource_url(request.context),
+ headers=headers)
diff --git a/docs/tutorials/wiki/src/views/CHANGES.txt b/docs/tutorials/wiki/src/views/CHANGES.txt
index 1544cf53b..35a34f332 100644
--- a/docs/tutorials/wiki/src/views/CHANGES.txt
+++ b/docs/tutorials/wiki/src/views/CHANGES.txt
@@ -1,3 +1,4 @@
-0.1
+0.0
+---
- Initial version
+- Initial version
diff --git a/docs/tutorials/wiki/src/views/README.txt b/docs/tutorials/wiki/src/views/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/views/README.txt
+++ b/docs/tutorials/wiki/src/views/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini
index 555010bed..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/views/development.ini
+++ b/docs/tutorials/wiki/src/views/development.ini
@@ -1,34 +1,44 @@
-[app:tutorial]
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
use = egg:tutorial
-reload_templates = true
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = true
-default_locale_name = en
-zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
-
-[pipeline:main]
-pipeline =
- egg:WebError#evalerror
- egg:repoze.zodbconn#closer
- egg:repoze.retry#retry
- tm
- tutorial
-
-[filter:tm]
-use = egg:repoze.tm2#tm
-commit_veto = repoze.tm:default_commit_veto
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_zodbconn
+ pyramid_tm
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
[server:main]
-use = egg:Paste#http
-host = 0.0.0.0
+use = egg:waitress#main
+host = 127.0.0.1
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
-keys = root
+keys = root, tutorial
[handlers]
keys = console
@@ -40,6 +50,11 @@ keys = generic
level = INFO
handlers = console
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -47,6 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini
index 5c47ade9b..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/views/production.ini
+++ b/docs/tutorials/wiki/src/views/production.ini
@@ -1,45 +1,36 @@
-[app:tutorial]
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
use = egg:tutorial
-reload_templates = false
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = false
-default_locale_name = en
-zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000
-[filter:weberror]
-use = egg:WebError#error_catcher
-debug = false
-;error_log =
-;show_exceptions_in_wsgi_errors = true
-;smtp_server = localhost
-;error_email = janitor@example.com
-;smtp_username = janitor
-;smtp_password = "janitor's password"
-;from_address = paste@localhost
-;error_subject_prefix = "Pyramid Error"
-;smtp_use_tls =
-;error_message =
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_tm
+ pyramid_zodbconn
-[filter:tm]
-use = egg:repoze.tm2#tm
-commit_veto = repoze.tm:default_commit_veto
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
-[pipeline:main]
-pipeline =
- weberror
- egg:repoze.zodbconn#closer
- egg:repoze.retry#retry
- tm
- tutorial
+###
+# wsgi server configuration
+###
[server:main]
-use = egg:Paste#http
+use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
keys = root, tutorial
@@ -66,6 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/views/setup.cfg b/docs/tutorials/wiki/src/views/setup.cfg
deleted file mode 100644
index 3d7ea6e23..000000000
--- a/docs/tutorials/wiki/src/views/setup.cfg
+++ /dev/null
@@ -1,28 +0,0 @@
-[nosetests]
-match=^test
-nocapture=1
-cover-package=tutorial
-with-coverage=1
-cover-erase=1
-
-[compile_catalog]
-directory = tutorial/locale
-domain = tutorial
-statistics = true
-
-[extract_messages]
-add_comments = TRANSLATORS:
-output_file = tutorial/locale/tutorial.pot
-width = 80
-
-[init_catalog]
-domain = tutorial
-input_file = tutorial/locale/tutorial.pot
-output_dir = tutorial/locale
-
-[update_catalog]
-domain = tutorial
-input_file = tutorial/locale/tutorial.pot
-output_dir = tutorial/locale
-previous = true
-
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index daa5e5eb1..beeed75c9 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -3,30 +3,39 @@ import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
-README = open(os.path.join(here, 'README.txt')).read()
-CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
requires = [
'pyramid',
- 'repoze.zodbconn',
- 'repoze.tm2>=1.0b1', # default_commit_veto
- 'repoze.retry',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'pyramid_zodbconn',
+ 'transaction',
'ZODB3',
- 'WebError',
+ 'waitress',
'docutils',
]
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='tutorial',
version='0.0',
description='tutorial',
- long_description=README + '\n\n' + CHANGES,
+ long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Intended Audience :: Developers",
- "Framework :: Pylons",
- "Programming Language :: Python",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -34,12 +43,12 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
- tests_require=requires,
- test_suite="tutorial",
- entry_points = """\
+ entry_points="""\
[paste.app_factory]
main = tutorial:main
""",
- paster_plugins=['pyramid'],
)
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index 04a01fead..f2a86df47 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -1,18 +1,18 @@
from pyramid.config import Configurator
-from repoze.zodbconn.finder import PersistentApplicationFinder
-from tutorial.models import appmaker
+from pyramid_zodbconn import get_connection
+from .models import appmaker
+
+
+def root_factory(request):
+ conn = get_connection(request)
+ return appmaker(conn.root())
+
def main(global_config, **settings):
- """ This function returns a WSGI application.
+ """ This function returns a Pyramid WSGI application.
"""
- 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)
- def get_root(request):
- return finder(request.environ)
- config = Configurator(root_factory=get_root, settings=settings)
- config.add_static_view('static', 'tutorial:static')
- config.scan('tutorial')
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/views/tutorial/models.py b/docs/tutorials/wiki/src/views/tutorial/models.py
index 9761856c6..aa907aee5 100644
--- a/docs/tutorials/wiki/src/views/tutorial/models.py
+++ b/docs/tutorials/wiki/src/views/tutorial/models.py
@@ -10,7 +10,7 @@ class Page(Persistent):
self.data = data
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = Wiki()
frontpage = Page('This is the front page')
app_root['FrontPage'] = frontpage
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/favicon.ico b/docs/tutorials/wiki/src/views/tutorial/static/favicon.ico
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/footerbg.png b/docs/tutorials/wiki/src/views/tutorial/static/footerbg.png
deleted file mode 100644
index 1fbc873da..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/footerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/headerbg.png b/docs/tutorials/wiki/src/views/tutorial/static/headerbg.png
deleted file mode 100644
index 0596f2020..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/headerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/ie6.css b/docs/tutorials/wiki/src/views/tutorial/static/ie6.css
deleted file mode 100644
index b7c8493d8..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/ie6.css
+++ /dev/null
@@ -1,8 +0,0 @@
-* html img,
-* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none",
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')",
-this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''),
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')",
-this.runtimeStyle.backgroundImage = "none")),this.pngSet=true)
-);}
-#wrap{display:table;height:100%}
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/middlebg.png b/docs/tutorials/wiki/src/views/tutorial/static/middlebg.png
deleted file mode 100644
index 2369cfb7d..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/middlebg.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pylons.css b/docs/tutorials/wiki/src/views/tutorial/static/pylons.css
deleted file mode 100644
index fd1914d8d..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/pylons.css
+++ /dev/null
@@ -1,65 +0,0 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-:focus{outline:0;}
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-table{border-collapse:collapse;border-spacing:0;}
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;}
-ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;}
-li{display:list-item;}
-ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;}
-ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;}
-ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;}
-.hidden{display:none;}
-p{line-height:1.5em;}
-h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;}
-#wrap{min-height:100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;}
-#header{background:#000000;top:0;font-size:14px;}
-#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;}
-.header,.footer{width:750px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#top-small,#bottom{width:100%;}
-#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;}
-#bottom{color:#222;background-color:#ffffff;}
-.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;}
-.top{padding-top:40px;}
-.top-small{padding-top:10px;}
-#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:350px;float:left;padding-right:25px;}
-#right{width:350px;float:right;padding-left:25px;}
-.align-left{text-align:left;}
-.align-right{text-align:right;}
-.align-center{text-align:center;}
-ul.links{margin:0;padding:0;}
-ul.links li{list-style-type:none;font-size:14px;}
-form{border-style:none;}
-fieldset{border-style:none;}
-input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;}
-input[type=text],input[type=password]{width:205px;}
-input[type=submit]{background-color:#ddd;font-weight:bold;}
-/*Opera Fix*/
-body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;}
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki/src/views/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pyramid-small.png b/docs/tutorials/wiki/src/views/tutorial/static/pyramid-small.png
deleted file mode 100644
index a5bc0ade7..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/pyramid-small.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/views/tutorial/static/pyramid.png
index 347e05549..4ab837be9 100644
--- a/docs/tutorials/wiki/src/views/tutorial/static/pyramid.png
+++ b/docs/tutorials/wiki/src/views/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/theme.css b/docs/tutorials/wiki/src/views/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/transparent.gif b/docs/tutorials/wiki/src/views/tutorial/static/transparent.gif
deleted file mode 100644
index 0341802e5..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/transparent.gif
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
index 6dbb0edde..b23f45d56 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
@@ -1,58 +1,69 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>${page.__name__} - Pyramid tutorial wiki (based on
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>${page.__name__} - Pyramid tutorial wiki (based on
TurboGears 20-Minute Wiki)</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon"
- href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/pylons.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/ie6.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top-small">
- <div class="top-small align-center">
- <div>
- <img width="220" height="50" alt="pyramid"
- src="${request.static_url('tutorial:static/pyramid-small.png')}" />
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ <form action="${save_url}" method="post">
+ <div class="form-group">
+ <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ </div>
+ <div class="form-group">
+ <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ </div>
+ </form>
+ </div>
+ </div>
</div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-right">
- <div id="left" class="app-welcome align-left">
- Editing <b><span tal:replace="page.__name__">Page Name Goes
- Here</span></b><br/>
- You can return to the
- <a href="${request.application_url}">FrontPage</a>.<br/>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
- <div id="right" class="app-welcome align-right"></div>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <form action="${save_url}" method="post">
- <textarea name="body" tal:content="page.data" rows="10"
- cols="60"/><br/>
- <input type="submit" name="form.submitted" value="Save"/>
- </form>
</div>
</div>
- </div>
- <div id="footer">
- <div class="footer"
- >&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
index d98420680..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
@@ -1,75 +1,67 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>The Pyramid Web Application Development Framework</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" />
- <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&amp;subset=latin" type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top">
- <div class="top align-center">
- <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-center">
- <p class="app-welcome">
- Welcome to <span class="app-name">${project}</span>, an application generated by<br/>
- the Pyramid web application development framework.
- </p>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <div id="left" class="align-right">
- <h2>Search documentation</h2>
- <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html">
- <input type="text" id="q" name="q" value="" />
- <input type="submit" id="x" value="Go" />
- </form>
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>ZODB Scaffold for The Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+ </div>
+ </div>
</div>
- <div id="right" class="align-left">
- <h2>Pyramid links</h2>
- <ul class="links">
- <li>
- <a href="http://pylonsproject.org">Pylons Website</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a>
- </li>
- <li>
- <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a>
- </li>
- <li>
- <a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
- </li>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
</ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
</div>
</div>
- </div>
- <div id="footer">
- <div class="footer">&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
index 537ae3a15..93580658b 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
@@ -1,61 +1,69 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon"
- href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/pylons.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/ie6.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top-small">
- <div class="top-small align-center">
- <div>
- <img width="220" height="50" alt="pyramid"
- src="${request.static_url('tutorial:static/pyramid-small.png')}" />
+<!DOCTYPE html>
+<html lang="${request.locale_name}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+
+ <title>${page.__name__} - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <div tal:replace="structure content">
+ Page text goes here.
+ </div>
+ <p>
+ <a tal:attributes="href edit_url" href="">
+ Edit this page
+ </a>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
+ <p>You can return to the
+ <a href="${request.application_url}">FrontPage</a>.
+ </p>
+ </div>
+ </div>
</div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-right">
- <div id="left" class="app-welcome align-left">
- Viewing <b><span tal:replace="page.__name__">Page Name Goes
- Here</span></b><br/>
- You can return to the
- <a href="${request.application_url}">FrontPage</a>.<br/>
- </div>
- <div id="right" class="app-welcome align-right"></div>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <div tal:replace="structure content">
- Page text goes here.
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
</div>
- <p>
- <a tal:attributes="href edit_url" href="">
- Edit this page
- </a>
- </p>
</div>
</div>
- </div>
- <div id="footer">
- <div class="footer"
- >&copy; Copyright 2008-2011, Agendaless Consulting.</div>
- </div>
-</body>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/tutorials/wiki/src/views/tutorial/tests.py b/docs/tutorials/wiki/src/views/tutorial/tests.py
index 28e424884..40f3c47af 100644
--- a/docs/tutorials/wiki/src/views/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/views/tutorial/tests.py
@@ -2,126 +2,16 @@ import unittest
from pyramid import testing
-class PageModelTests(unittest.TestCase):
- def _getTargetClass(self):
- from tutorial.models import Page
- return Page
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
- def _makeOne(self, data=u'some data'):
- return self._getTargetClass()(data=data)
+ def tearDown(self):
+ testing.tearDown()
- def test_constructor(self):
- instance = self._makeOne()
- self.assertEqual(instance.data, u'some data')
-
-class WikiModelTests(unittest.TestCase):
-
- def _getTargetClass(self):
- from tutorial.models import Wiki
- return Wiki
-
- def _makeOne(self):
- return self._getTargetClass()()
-
- def test_it(self):
- wiki = self._makeOne()
- self.assertEqual(wiki.__parent__, None)
- self.assertEqual(wiki.__name__, None)
-
-class AppmakerTests(unittest.TestCase):
- def _callFUT(self, zodb_root):
- from tutorial.models import appmaker
- return appmaker(zodb_root)
-
- def test_it(self):
- root = {}
- self._callFUT(root)
- self.assertEqual(root['app_root']['FrontPage'].data,
- 'This is the front page')
-
-class ViewWikiTests(unittest.TestCase):
- def test_it(self):
- from tutorial.views import view_wiki
- context = testing.DummyResource()
- request = testing.DummyRequest()
- response = view_wiki(context, request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
-
-class ViewPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from tutorial.views import view_page
- return view_page(context, request)
-
- def test_it(self):
- wiki = testing.DummyResource()
- wiki['IDoExist'] = testing.DummyResource()
- context = testing.DummyResource(data='Hello CruelWorld IDoExist')
- context.__parent__ = wiki
- context.__name__ = 'thepage'
- request = testing.DummyRequest()
- info = self._callFUT(context, request)
- self.assertEqual(info['page'], context)
- 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/thepage/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from tutorial.views import add_page
- return add_page(context, request)
-
- def test_it_notsubmitted(self):
- from pyramid.url import resource_url
- context = testing.DummyResource()
- request = testing.DummyRequest()
- request.subpath = ['AnotherPage']
- info = self._callFUT(context, request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(
- info['save_url'],
- resource_url(context, request, 'add_page', 'AnotherPage'))
-
- def test_it_submitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.subpath = ['AnotherPage']
- self._callFUT(context, request)
- page = context['AnotherPage']
- self.assertEqual(page.data, 'Hello yo!')
- self.assertEqual(page.__name__, 'AnotherPage')
- self.assertEqual(page.__parent__, context)
-
-class EditPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from tutorial.views import edit_page
- return edit_page(context, request)
-
- def test_it_notsubmitted(self):
- from pyramid.url import resource_url
- context = testing.DummyResource()
+ def test_my_view(self):
+ from .views import my_view
request = testing.DummyRequest()
- info = self._callFUT(context, request)
- self.assertEqual(info['page'], context)
- self.assertEqual(info['save_url'],
- resource_url(context, request, 'edit_page'))
-
- def test_it_submitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- response = self._callFUT(context, request)
- self.assertEqual(response.location, 'http://example.com/')
- self.assertEqual(context.data, 'Hello yo!')
-
-
-
+ info = my_view(request)
+ self.assertEqual(info['project'], 'tutorial')
diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py
index 42420f2fe..61517c31d 100644
--- a/docs/tutorials/wiki/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki/src/views/tutorial/views.py
@@ -2,20 +2,18 @@ from docutils.core import publish_parts
import re
from pyramid.httpexceptions import HTTPFound
-from pyramid.url import resource_url
from pyramid.view import view_config
-from tutorial.models import Page
+from .models import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='tutorial.models.Wiki')
+@view_config(context='.models.Wiki')
def view_wiki(context, request):
- return HTTPFound(location=resource_url(context, request, 'FrontPage'))
+ return HTTPFound(location=request.resource_url(context, 'FrontPage'))
-@view_config(context='tutorial.models.Page',
- renderer='tutorial:templates/view.pt')
+@view_config(context='.models.Page', renderer='templates/view.pt')
def view_page(context, request):
wiki = context.__parent__
@@ -23,7 +21,7 @@ def view_page(context, request):
word = match.group(1)
if word in wiki:
page = wiki[word]
- view_url = resource_url(page, request)
+ view_url = request.resource_url(page)
return '<a href="%s">%s</a>' % (view_url, word)
else:
add_url = request.application_url + '/add_page/' + word
@@ -31,34 +29,32 @@ def view_page(context, request):
content = publish_parts(context.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
- edit_url = resource_url(context, request, 'edit_page')
+ edit_url = request.resource_url(context, 'edit_page')
return dict(page = context, content = content, edit_url = edit_url)
-@view_config(name='add_page', context='tutorial.models.Wiki',
- renderer='tutorial:templates/edit.pt')
+@view_config(name='add_page', context='.models.Wiki',
+ renderer='templates/edit.pt')
def add_page(context, request):
- name = request.subpath[0]
+ pagename = request.subpath[0]
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(body)
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
- context[name] = page
- return HTTPFound(location = resource_url(page, request))
- save_url = resource_url(context, request, 'add_page', name)
+ context[pagename] = page
+ return HTTPFound(location = request.resource_url(page))
+ save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
- page.__name__ = name
+ page.__name__ = pagename
page.__parent__ = context
return dict(page = page, save_url = save_url)
-@view_config(name='edit_page', context='tutorial.models.Page',
- renderer='tutorial:templates/edit.pt')
+@view_config(name='edit_page', context='.models.Page',
+ renderer='templates/edit.pt')
def edit_page(context, request):
if 'form.submitted' in request.params:
context.data = request.params['body']
- return HTTPFound(location = resource_url(context, request))
+ return HTTPFound(location = request.resource_url(context))
- return dict(page = context,
- save_url = resource_url(context, request, 'edit_page'))
-
-
+ return dict(page=context,
+ save_url=request.resource_url(context, 'edit_page'))
diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst
index c843a0129..788ec595b 100644
--- a/docs/tutorials/wiki/tests.rst
+++ b/docs/tutorials/wiki/tests.rst
@@ -1,78 +1,75 @@
+.. _wiki_adding_tests:
+
============
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.
+We will now add tests for the models and the views and a few functional tests
+in ``tests.py``. Tests ensure that an application works, and that it
+continues to work when changes are made in the future.
-Testing the Models
-==================
+Test the models
+===============
-We write tests for the model
-classes and the appmaker. Changing ``tests.py``, we'll write a separate test
-class for each model class, and we'll write a test class for the
-``appmaker``.
+We write tests for the ``model`` classes and the ``appmaker``. Changing
+``tests.py``, we'll write a separate test class for each ``model`` class, and
+we'll write a test class for the ``appmaker``.
-To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a
-result of the ``pyramid_zodb`` project generator. We'll add three test
-classes: one for the ``Page`` model named ``PageModelTests``, one for the
-``Wiki`` model named ``WikiModelTests``, and one for the appmaker named
-``AppmakerTests``.
+To do so, we'll retain the ``tutorial.tests.ViewTests`` class that was
+generated as part of the ``zodb`` scaffold. We'll add three test classes: one
+for the ``Page`` model named ``PageModelTests``, one for the ``Wiki`` model
+named ``WikiModelTests``, and one for the appmaker named ``AppmakerTests``.
-Testing the Views
-=================
+Test 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.
-
+added previously. As a result, we'll *delete* the ``ViewTests`` class that
+the ``zodb`` scaffold provided, and add four other test classes:
+``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``.
+These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page``
+views.
Functional tests
================
-We test the whole application, covering security aspects that are not
+We'll 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``
-====================================================
+View 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:
+Open the ``tutorial/tests.py`` module, and edit it such that it appears as
+follows:
.. literalinclude:: src/tests/tutorial/tests.py
:linenos:
:language: python
-Running the Tests
+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:
+We can run these tests by using ``py.test`` similarly to how we did in
+:ref:`running_tests`. Our testing dependencies have already been satisfied,
+courtesy of the scaffold, so we can jump right to running tests.
On UNIX:
.. code-block:: text
- $ ../bin/python setup.py test -q
+ $ $VENV/bin/py.test tutorial/tests.py -q
On Windows:
.. code-block:: text
- c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test tutorial/tests.py -q
-The expected result looks something like:
+The expected result should look like the following:
.. code-block:: text
- .........
- ----------------------------------------------------------------------
- Ran 23 tests in 1.653s
-
- OK
+ ........................
+ 24 passed in 2.46 seconds