summaryrefslogtreecommitdiff
path: root/docs/tutorials
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tutorials')
-rw-r--r--docs/tutorials/wiki/authorization.rst460
-rw-r--r--docs/tutorials/wiki/background.rst5
-rw-r--r--docs/tutorials/wiki/basiclayout.rst355
-rw-r--r--docs/tutorials/wiki/definingmodels.rst151
-rw-r--r--docs/tutorials/wiki/definingviews.rst477
-rw-r--r--docs/tutorials/wiki/design.rst96
-rw-r--r--docs/tutorials/wiki/distributing.rst18
-rw-r--r--docs/tutorials/wiki/installation.rst181
-rw-r--r--docs/tutorials/wiki/src/authorization/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/authorization/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.py8
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/authorization/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt83
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt59
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt57
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt81
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views/default.py (renamed from docs/tutorials/wiki/src/tests/tutorial/views.py)49
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py12
-rw-r--r--docs/tutorials/wiki/src/basiclayout/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/basiclayout/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt62
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt70
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views.py7
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki/src/installation/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/installation/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/installation/setup.py8
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/installation/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt62
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt70
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views.py7
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views/default.py8
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki/src/models/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/models/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/models/setup.py8
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/models/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/layout.pt62
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt70
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views.py7
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views/default.py8
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki/src/tests/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/tests/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/tests/setup.py8
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/tests/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt83
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt59
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/login.pt57
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/view.pt81
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/tests.py14
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views/default.py (renamed from docs/tutorials/wiki/src/authorization/tutorial/views.py)49
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views/notfound.py12
-rw-r--r--docs/tutorials/wiki/src/views/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/views/pytest.ini2
-rw-r--r--docs/tutorials/wiki/src/views/setup.py8
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/__init__.py7
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/models/__init__.py (renamed from docs/tutorials/wiki/src/views/tutorial/models.py)0
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/pshell.py1
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/routes.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/theme.css3
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/404.pt10
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/edit.pt79
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/layout.pt59
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/view.pt78
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/tests.py3
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views/default.py (renamed from docs/tutorials/wiki/src/views/tutorial/views.py)26
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/views/notfound.py12
-rw-r--r--docs/tutorials/wiki/tests.rst55
-rw-r--r--docs/tutorials/wiki2/background.rst2
-rw-r--r--docs/tutorials/wiki2/installation.rst10
-rw-r--r--docs/tutorials/wiki2/src/authentication/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/authorization/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/installation/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/models/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/default.py2
-rw-r--r--docs/tutorials/wiki2/src/views/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/default.py2
120 files changed, 1751 insertions, 1732 deletions
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index b7eeb19ae..ef914cab5 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -1,39 +1,33 @@
.. _wiki_adding_authorization:
-====================
-Adding authorization
-====================
-
-: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 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
+=======================================
+Adding authorization and authentication
+=======================================
+
+:app:`Pyramid` provides facilities for :term:`authentication` and :term:`authorization`.
+We will 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 will change that to allow only people who are members of a *group* named ``group:editors`` to add and edit wiki pages.
+We will continue to allow anyone with access to the server to view pages.
+
+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.
We will implement the access control with the following steps:
-* Add password hashing dependencies.
-* 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``).
+- Add password hashing dependencies.
+- 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``).
Then we will add the login and logout features:
-* 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``).
+- 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``).
Access control
@@ -43,14 +37,15 @@ Access control
Add dependencies
~~~~~~~~~~~~~~~~
-Just like in :ref:`wiki_defining_views`, we need a new dependency. We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ package, to our tutorial package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function.
+Just like in :ref:`wiki_defining_views`, we need a new dependency.
+We need to add the `bcrypt <https://pypi.org/project/bcrypt/>`_ 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/authorization/setup.py
- :linenos:
- :emphasize-lines: 23
- :language: python
+ :linenos:
+ :emphasize-lines: 23
+ :language: python
Only the highlighted line needs to be added.
@@ -58,7 +53,9 @@ Do not forget to run ``pip install -e .`` just like in :ref:`wiki-running-pip-in
.. note::
- We are using the ``bcrypt`` package from PyPI to hash our passwords securely. There are other one-way hash algorithms for passwords if bcrypt is an issue on your system. Just make sure that it's an algorithm approved for storing passwords versus a generic one-way hash.
+ We are using the ``bcrypt`` package from PyPI to hash our passwords securely.
+ There are other one-way hash algorithms for passwords if bcrypt is an issue on your system.
+ Just make sure that it is an algorithm approved for storing passwords versus a generic one-way hash.
Add users and groups
@@ -67,78 +64,78 @@ Add users and groups
Create a new ``tutorial/security.py`` module with the following content:
.. literalinclude:: src/authorization/tutorial/security.py
- :linenos:
- :language: python
+ :linenos:
+ :language: python
-The ``groupfinder`` function accepts a userid and a request and
-returns one of these values:
+The ``groupfinder`` function accepts a ``userid`` and a ``request``
+It returns one of these values:
-- If ``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``.
+- If ``userid`` exists in the system, it will return either a sequence of group identifiers, or an empty sequence if the user is not 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.
+For example:
+
+- ``groupfinder('editor', request )`` returns ``['group:editor']``.
+- ``groupfinder('viewer', request)`` returns ``[]``.
+- ``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.
There are two helper methods that will help us later to authenticate users.
The first is ``hash_password`` which takes a raw password and transforms it using
-bcrypt into an irreversible representation, a process known as "hashing". The
-second method, ``check_password``, will allow us to compare the hashed value of the
-submitted password against the hashed value of the password stored in the user's
-record. If the two hashed values match, then the submitted
-password is valid, and we can authenticate the user.
+bcrypt into an irreversible representation, a process known as "hashing".
+The second method, ``check_password``, will allow us to compare the hashed value of the submitted password against the hashed value of the password stored in the user's
+record.
+If the two hashed values match, then the submitted password is valid, and we can authenticate the user.
-We hash passwords so that it is impossible to decrypt and use them to
-authenticate in the application. If we stored passwords foolishly in clear text,
-then anyone with access to the database could retrieve any password to authenticate
-as any user.
+We hash passwords so that it is impossible to decrypt and use them to authenticate in the application.
+If we stored passwords foolishly in clear text, then anyone with access to the database could retrieve any password to authenticate as any user.
In a production system, user and group data will most often be saved and come from a
-database, but here we use "dummy" data to represent user and groups sources.
+database.
+Here we use "dummy" data to represent user and groups sources.
+
Add an ACL
~~~~~~~~~~
-Open ``tutorial/models.py`` and add the following import
-statement near the top:
+Open ``tutorial/models.py`` and add the following import statement near the top:
-.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 4-8
- :lineno-match:
- :language: python
+.. literalinclude:: src/authorization/tutorial/models/__init__.py
+ :lines: 4-8
+ :lineno-match:
+ :language: python
Add the following lines to the ``Wiki`` class:
-.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 9-13
- :lineno-match:
- :emphasize-lines: 4-5
- :language: python
-
-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.
-
-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.
-
-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.
+.. literalinclude:: src/authorization/tutorial/models/__init__.py
+ :lines: 9-13
+ :lineno-match:
+ :emphasize-lines: 4-5
+ :language: python
+
+We import :data:`~pyramid.security.Allow`, an action which means that
+permission is allowed.
+We also import :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.
+
+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.
+
+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 is only happenstance that we assigned 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.
+
+.. seealso::
+
+ See :ref:`assigning_acls` for more information about what an :term:`ACL` represents.
+
Add authentication and authorization policies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -147,123 +144,113 @@ Open ``tutorial/__init__.py`` and add the highlighted import
statements:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 1-8
- :linenos:
- :emphasize-lines: 3-6,8
- :language: python
+ :lines: 1-8
+ :linenos:
+ :emphasize-lines: 3-6,8
+ :language: python
Now add those policies to the configuration:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 18-25
- :lineno-match:
- :emphasize-lines: 2-4,6-7
- :language: python
+ :lines: 15-25
+ :lineno-match:
+ :emphasize-lines: 4-6,8-9
+ :language: python
Only the highlighted lines need to be added.
-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.
+We enabled an ``AuthTktAuthenticationPolicy`` which is based in an auth ticket that may be included in the request.
+We also enabled an ``ACLAuthorizationPolicy`` which uses an ACL to determine the *allow* or *deny* outcome for a view.
+
+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 earlier.
-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.
Add permission declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter
-to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 49-51
- :emphasize-lines: 2-3
- :language: python
+Open ``tutorial/views/default.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
+
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 49-51
+ :emphasize-lines: 2-3
+ :language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 68-70
- :emphasize-lines: 2-3
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 68-70
+ :emphasize-lines: 2-3
+ :language: python
-Only the highlighted lines, along with their preceding commas, need to be
-edited and added.
+Only the highlighted lines, along with their preceding commas, need to be edited and added.
-The result is that only users who possess the ``edit`` permission at the time
-of the request may invoke those two views.
+The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views.
Add a ``permission='view'`` parameter to the ``@view_config`` decorator for
``view_wiki()`` and ``view_page()`` as follows:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 23-24
- :emphasize-lines: 1-2
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 23-24
+ :emphasize-lines: 1-2
+ :language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 28-29
- :emphasize-lines: 1-2
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.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.
+Only the highlighted lines, along with their preceding commas, need to be edited and added.
This allows anyone to invoke these two views.
-We are done with the changes needed to control access. The changes that
-follow will add the login and logout feature.
+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 will 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 will 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.
-Add the following import statements to the head of
-``tutorial/views.py``:
+Add the following import statements to the head of ``tutorial/views/default.py``:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 6-17
- :emphasize-lines: 1-12
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 4-15
+ :emphasize-lines: 2-10,12
+ :language: python
All the highlighted lines need to be added or edited.
-: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.
+: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.
Now add the ``login`` and ``logout`` views at the end of the file:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 80-
- :lineno-match:
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 78-
+ :lineno-match:
+ :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.
+- 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, then they will be shown the login form before being allowed to continue.
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``.
+``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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -271,134 +258,127 @@ Add the ``login.pt`` Template
Create ``tutorial/templates/login.pt`` with the following content:
.. literalinclude:: src/authorization/tutorial/templates/login.pt
- :language: html
+ :language: html
+
+The above template is referenced in the login view that we just added in ``views.py``.
-The above template is referenced in the login view that we just added in
-``views.py``.
Return a ``logged_in`` flag to the renderer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to
-the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as
-follows:
+Open ``tutorial/views/default.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: 46-47
- :emphasize-lines: 1-2
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 45-46
+ :emphasize-lines: 1-2
+ :language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 65-66
- :emphasize-lines: 1-2
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 65-66
+ :emphasize-lines: 1-2
+ :language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 76-78
- :emphasize-lines: 2-3
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 77-79
+ :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.
+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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/templates/edit.pt`` and
-``tutorial/templates/view.pt`` and add the following code as
-indicated by the highlighted lines.
+Open ``tutorial/templates/edit.pt`` and ``tutorial/templates/view.pt``.
+Add the following code as indicated by the highlighted lines.
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :lines: 35-39
- :emphasize-lines: 2-4
- :language: html
+ :lines: 4-8
+ :emphasize-lines: 2-4
+ :language: html
-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
+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.
+
Reviewing our changes
---------------------
-Our ``tutorial/__init__.py`` will look like this when we're done:
+Our ``tutorial/__init__.py`` will look like this when we are done:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :linenos:
- :emphasize-lines: 4-5,8,19-21,23-24
- :language: python
+ :linenos:
+ :emphasize-lines: 3-6,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:
+Our ``tutorial/models.py`` will look like this when we are done:
-.. literalinclude:: src/authorization/tutorial/models.py
- :linenos:
- :emphasize-lines: 4-7,12-13
- :language: python
+.. literalinclude:: src/authorization/tutorial/models/__init__.py
+ :linenos:
+ :emphasize-lines: 4-8,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:
+Our ``tutorial/views/default.py`` will look like this when we are done:
-.. literalinclude:: src/authorization/tutorial/views.py
- :linenos:
- :emphasize-lines: 8,11-15,17,24,29,47,51,66,70,78,80-
- :language: python
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :linenos:
+ :emphasize-lines: 5-12,15,21-22,27-28,45-46,50-51,65-66,70-71,78-
+ :language: python
Only the highlighted lines need to be added or edited.
-Our ``tutorial/templates/edit.pt`` template will look like this when
-we're done:
+Our ``tutorial/templates/edit.pt`` template will look like this when we are done:
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :linenos:
- :emphasize-lines: 36-38
- :language: html
+ :linenos:
+ :emphasize-lines: 5-7
+ :language: html
Only the highlighted lines need to be added or edited.
-Our ``tutorial/templates/view.pt`` template will look like this when
-we're done:
+Our ``tutorial/templates/view.pt`` template will look like this when we are done:
.. literalinclude:: src/authorization/tutorial/templates/view.pt
- :linenos:
- :emphasize-lines: 36-38
- :language: html
+ :linenos:
+ :emphasize-lines: 5-7
+ :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.
+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 is 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, then a login form will be displayed.
+ Supplying the credentials with the username ``editor`` and 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 will see a Logout link in the upper right hand corner.
+ When we click it, we are logged out, and redirected back to the front page.
+
+- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter.
diff --git a/docs/tutorials/wiki/background.rst b/docs/tutorials/wiki/background.rst
index c10ab9e55..1a076de85 100644
--- a/docs/tutorials/wiki/background.rst
+++ b/docs/tutorials/wiki/background.rst
@@ -15,9 +15,4 @@ To code along with this tutorial, the developer will need a Unix
machine with development tools (macOS with XCode, any Linux or BSD
variant, and so on) *or* a Windows system of any kind.
-.. warning::
-
- 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 49ee6902e..52d3f4670 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -4,188 +4,255 @@
Basic Layout
============
-The starter files generated by selecting the ``zodb`` backend in the
-cookiecutter are very basic, but they provide a good orientation for the
-high-level patterns common to most :term:`traversal`-based (and
-:term:`ZODB`-based) :app:`Pyramid` projects.
+The starter files generated by the cookiecutter are very basic, but they provide a good orientation for the high-level patterns common to most :term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects.
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. 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.
+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.
+We use ``__init__.py`` both as a marker, indicating the directory in which it is 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 :term:`Setuptools` :term:`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 ``pserve`` command using the ``development.ini`` generated configuration file, the application configuration points at a :term:`Setuptools` :term:`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:
+Open ``tutorial/__init__.py``.
+It should already contain the following:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :linenos:
- :language: py
+ :linenos:
+ :language: py
+
+Let's go over this piece-by-piece.
+First we need some imports to support later code.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :end-before: root_factory
+ :lineno-match:
+ :language: py
+
+Define a :term:`root factory` for our Pyramid application.
+It establishes a connection to ZODB database.
+It returns an ``appmaker``, which we will describe in the next section :ref:`wiki-resources-and-models`.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :pyobject: root_factory
+ :lineno-match:
+ :language: py
+
+``__init__.py`` defines a function named ``main``.
+Here is the entirety of the ``main`` function that we have defined in our ``__init__.py``:
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :pyobject: main
+ :lineno-match:
+ :language: py
+
+When you invoke the ``pserve development.ini`` command, the ``main`` function above is executed.
+It accepts some settings and returns a :term:`WSGI` application.
+See :ref:`startup_chapter` for more about ``pserve``.
+
+Next in ``main``, construct a :term:`Configurator` object using a context manager.
+See also :term:`Deployment settings`.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 14
+ :lineno-match:
+ :language: py
+
+``settings`` is passed to the ``Configurator`` as a keyword argument with the dictionary values passed as the ``**settings`` argument.
+This will be a dictionary of settings parsed from the ``.ini`` file, which contains
+deployment-related values, such as ``pyramid.reload_templates``, ``zodbconn.uri``, and so on.
+
+Next include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.org/project/transaction/>`_ package.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 15
+ :lineno-match:
+ :language: py
+
+Next include support for ``pyramid_retry`` to retry a request when transient exceptions occur.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 16
+ :lineno-match:
+ :language: py
+
+Next include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 17
+ :lineno-match:
+ :language: py
+
+Next set a root factory using our function named ``root_factory``.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 18
+ :lineno-match:
+ :language: py
+
+Next include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates.
-#. *Lines 1-3*. Perform some dependency imports.
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 19
+ :lineno-match:
+ :language: py
-#. *Lines 6-8*. Define a :term:`root factory` for our Pyramid application.
+Next include routes from the ``.routes`` module.
-#. *Line 11*. ``__init__.py`` defines a function named ``main``.
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 20
+ :lineno-match:
+ :language: py
-#. *Line 14*. Use an explicit transaction manager for apps so that they do not implicitly create new transactions when touching the manager outside of the ``pyramid_tm`` lifecycle.
+This registers a "static view" using the :meth:`pyramid.config.Configurator.add_static_view` method.
+This view answers requests whose URL path starts with ``/static``.
+This statement registers a view that will serve up static assets, such as CSS and image files.
+In this case the URL will answer requests at ``http://localhost:6543/static/`` and below.
-#. *Line 15*. Construct a :term:`Configurator` as a :term:`context manager` with the settings keyword parsed by :term:`PasteDeploy`.
+The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``.
-#. *Line 16*. Include support for the :term:`Chameleon` template rendering
- bindings, allowing us to use the ``.pt`` templates.
+The second argument of this method is the "path".
+It is a relative :term:`asset specification`.
+It finds the resources it should serve within the ``static`` directory inside the ``tutorial`` package.
+Alternatively the cookiecutter could have used an *absolute* asset specification as the path (``tutorial:static``).
-#. *Line 17*. Include support for ``pyramid_tm``, allowing Pyramid requests to join the active transaction as provided by the `transaction <https://pypi.org/project/transaction/>`_ package.
+The third argument is an optional ``cache_max_age`` which specifies the number of seconds the static asset will be HTTP-cached.
-#. *Line 18*. Include support for ``pyramid_retry`` to retry a request when transient exceptions occur.
+Next perform a :term:`scan`.
-#. *Line 19*. Include support for ``pyramid_zodbconn``, providing integration between :term:`ZODB` and a Pyramid application.
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 21
+ :lineno-match:
+ :language: py
-#. *Line 20*. Set a root factory using our function named ``root_factory``.
+A scan will find :term:`configuration decoration`, such as view configuration decorators (e.g., ``@view_config``) in the source code of the ``tutorial`` package.
+It 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 cookiecutter could have equivalently said ``config.scan('tutorial')``, but it chose to omit the package name argument.
-#. *Line 21*. 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 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 cookiecutter could have used an *absolute* asset
- specification as the path (``tutorial:static``).
+Finally use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application.
-#. *Line 22*. 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. 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 cookiecutter could have equivalently said ``config.scan('tutorial')``, but
- it chose to omit the package name argument.
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 22
+ :lineno-match:
+ :language: py
-#. *Line 23*. Use the
- :meth:`pyramid.config.Configurator.make_wsgi_app` method
- to return a :term:`WSGI` application.
-Resources and models with ``models.py``
----------------------------------------
+.. _wiki-resources-and-models:
-:app:`Pyramid` uses the word :term:`resource` to describe objects arranged
-hierarchically in a :term:`resource tree`. This tree is consulted by
-:term:`traversal` to map URLs to code. In this application, the resource
-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 ``zodb`` cookiecutter put the classes that implement our
-resource objects, each of which also happens to be a domain model object.
+Resources and models with ``models`` package
+--------------------------------------------
+
+:app:`Pyramid` uses the word :term:`resource` to describe objects arranged hierarchically in a :term:`resource tree`.
+This tree is consulted by :term:`traversal` to map URLs to code.
+In this application, the resource tree represents the site structure, but it *also* represents the :term:`domain model` of the application.
+Each resource is a node stored persistently in a :term:`ZODB` database.
+The ``models.py`` file is where the ``zodb`` cookiecutter 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
+.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:linenos:
:language: python
-#. *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, set these to ``None`` to indicate that this is the
- :term:`root` object.
-
-#. *Lines 8-12*. ``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. It is used by the ``root_factory`` we've defined
- in our ``__init__.py``.
-
- 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``
------------------------
-
-Our cookiecutter generated a default ``views.py`` on our behalf. It
-contains a single view, which is used to render the page shown when you visit
-the URL ``http://localhost:6543/``.
-
-Here is the source for ``views.py``:
-
-.. literalinclude:: src/basiclayout/tutorial/views.py
- :linenos:
- :language: python
+#. *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, these are set to ``None`` to indicate that this is the :term:`root` object.
+
+#. *Lines 8-12*.
+ ``appmaker`` is used to return the *application root* object.
+ It is called on *every request* to the :app:`Pyramid` application by virtue of the ``root_factory`` defined in our ``__init__.py``.
+ It also performs bootstrapping by *creating* an application root (inside the ZODB root object) if one does not already exist.
+
+ Bootstrapping is done by first seeing if the database has the persistent application root.
+ If not, then we make an instance, store it, and commit the transaction.
+
+ We then return the application root object.
+
+
+View declarations via the ``views`` package
+-------------------------------------------
+
+Our cookiecutter generated a default ``views`` package on our behalf.
+It contains a two views.
+
+The first view is used to render the page shown when you visit the URL ``http://localhost:6543/``.
+Open ``tutorial/views/default.py`` in the ``views`` package.
+It should already contain the following:
+
+.. literalinclude:: src/basiclayout/tutorial/views/default.py
+ :linenos:
+ :language: python
Let's try to understand the components in this module:
-#. *Lines 1-2*. Perform some dependency imports.
-
-#. *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 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``.
-
- The ``context`` argument signifies that the decorated view callable should
- only be run when :term:`traversal` finds the ``tutorial.models.MyModel``
- :term:`resource` to be the :term:`context` of a request. In English, this
- means that when the URL ``/`` is visited, because ``MyModel`` is the root
- model, this view callable will be invoked.
-
- The ``renderer`` argument names an :term:`asset specification` of
- ``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. 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 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 ``zodb`` cookiecutter 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.
-
- The function returns the dictionary ``{'project':'tutorial'}``. This
- dictionary is used by the template named by the ``mytemplate.pt`` asset
- specification to fill in certain values on the page.
+#. *Lines 1-3*.
+ Perform some dependency imports.
+
+#. *Line 6*.
+ 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.
+ Remember in our application's ``__init__.py`` when we executed the :meth:`pyramid.config.Configurator.scan` method ``config.scan()``?
+ By calling the scan method, Pyramid's configurator will find and process this ``@view_config`` decorator, and create a view configuration within our application.
+ Without being processed by ``scan``, the decorator effectively does nothing.
+ ``@view_config`` is inert without being detected via a :term:`scan`.
+
+ The ``@view_config`` decorator accepts a number of keyword arguments.
+ We use two keyword arguments here: ``context`` and ``renderer``.
+
+ The ``context`` argument signifies that the decorated view callable ``my_view`` should only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` :term:`resource` as the :term:`context` of a request.
+ In English this means that when the URL ``/`` is visited, and because ``MyModel`` is the root model, this view callable will be invoked.
+
+ The ``renderer`` argument names an :term:`asset specification` of ``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 will see a ``mytemplate.pt`` template file
+ This template renders the default home page of the generated project.
+ This asset specification is *relative* to the ``views`` package.
+ Alternatively we could have used the absolute asset specification ``tutorial:templates/mytemplate.pt``.
+
+ 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 7-8*.
+ A :term:`view callable` named ``my_view`` is defined, which is decorated in the step above.
+ This view callable is a *function* generated by the cookiecutter.
+ It is given a single argument, ``request``.
+ This is the standard call signature for a Pyramid :term:`view callable`.
+ The function returns the dictionary ``{'project': 'myproj'}``.
+ This dictionary is used by the template named by the ``mytemplate.pt`` asset specification to fill in certain values on the page.
+
+Let us open ``tutorial/views/default.py`` in the ``views`` package to look at the second view.
+
+.. literalinclude:: src/basiclayout/tutorial/views/notfound.py
+ :linenos:
+ :language: python
+
+Without repeating ourselves, we will point out the differences between this view and the previous.
+
+#. *Line 4*.
+ The ``notfound_view`` function is decorated with ``@notfound_view_config``.
+ This decorator registers a :term:`Not Found View` using :meth:`pyramid.config.Configurator.add_notfound_view`.
+
+ The ``renderer`` argument names an :term:`asset specification` of ``templates/404.pt``.
+
+#. *Lines 5-7*.
+ A :term:`view callable` named ``notfound_view`` is defined, which is decorated in the step above.
+ It sets the HTTP response status code to ``404``.
+ The function returns an empty dictionary to the template ``404.pt``, which accepts no parameters anyway.
+
Configuration in ``development.ini``
------------------------------------
-The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as
-opposed to the ``tutorial`` :term:`package` directory) looks like this:
+The ``development.ini`` (in the ``tutorial`` :term:`project` directory, as opposed to the ``tutorial`` :term:`package` directory) looks like this:
.. literalinclude:: src/basiclayout/development.ini
:language: ini
-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
-``pserve``.
+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.
+When the server is started via ``pserve``, the values within this section are passed as ``**settings`` to the ``main`` function defined in ``__init__.py``.
diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst
index e973cfdfe..3a340e6f7 100644
--- a/docs/tutorials/wiki/definingmodels.rst
+++ b/docs/tutorials/wiki/definingmodels.rst
@@ -4,90 +4,111 @@
Defining the Domain Model
=========================
-The first change we'll make to our stock cookiecutter-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.
+Let's make changes to our stock cookiecutter-generated application.
+We will 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 will do this inside our ``models.py`` file.
-Because we're using :term:`ZODB` to represent our
-:term:`resource tree`, each of these resource constructors represents a
-:term:`domain model` object, so we'll call these constructors "model
-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.
+Because we are using :term:`ZODB` to represent our :term:`resource tree`, each of these resource constructors represents a :term:`domain model` object.
+We will call these constructors "model 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.
+
+.. seealso::
+
+ We will introduce a lot of concepts throughout the remainder of this tutorial.
+ See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid.
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
-our persistent storage (represented on disk as a file named ``Data.fs``),
-we'll have strange things happen the next time we want to visit the
-application in a browser. Remove the ``Data.fs`` from the ``tutorial``
-directory before proceeding any further. It's always fine to do this as long
-as you don't care about the content of the database; the database itself will
-be recreated as necessary.
+In the next step, we will remove the ``MyModel`` Python model class from our ``models`` package.
+Since this class is referred to within our persistent storage (represented on disk as a file named ``Data.fs``), we will have strange things happen the next time we want to visit the application in a browser.
+
+Remove the ``Data.fs`` from the ``tutorial`` directory before proceeding any further.
+It is 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.
-Edit ``models.py``
-------------------
+
+Edit ``models`` package
+-----------------------
.. note::
- 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.
+ There is nothing special about the package name ``models``.
+ A project may have many models throughout its codebase in arbitrarily named files and directories.
+ Files that implement models often have ``model`` in their names, 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:
+Open ``tutorial/models/__init__.py`` file and edit it to look like the following:
-.. literalinclude:: src/models/tutorial/models.py
+.. literalinclude:: src/models/tutorial/models/__init__.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.
-
-Then we'll add an import at the top for the :class:`persistent.Persistent` class. We'll use this for a new ``Page`` class in a moment.
-
-Then we'll add a ``Wiki`` class. We want it to inherit from the
-:class:`persistent.mapping.PersistentMapping` class because it provides
-mapping behavior, and it makes sure that our Wiki page is stored as a
-"first-class" persistent object in our ZODB database.
-
-Our ``Wiki`` class should have two attributes set to ``None`` at
-class scope: ``__parent__`` and ``__name__``. If a model has a
-``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid`
-application, it means that it's the :term:`root` model. The ``__name__``
-of the root model is also always ``None``.
-
-Then we'll add a ``Page`` class. This class should inherit from the
-:class:`persistent.Persistent` class. We'll also give it an ``__init__``
-method that accepts a single parameter named ``data``. This parameter will
-contain the :term:`reStructuredText` body representing the wiki page content.
-Note that ``Page`` objects don't have an initial ``__name__`` or
-``__parent__`` attribute. All objects in a traversal graph must have a
-``__name__`` and a ``__parent__`` attribute. We don't specify these here
-because both ``__name__`` and ``__parent__`` will be set by 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
-``models.py`` file so that the :term:`root` :term:`resource` of our
-application is a Wiki instance. We'll also slot a single page object (the
-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.
+Remove the ``MyModel`` class from the generated ``models/__init__.py`` file.
+The ``MyModel`` class is only a sample and we're not going to use it.
+
+Next we add an import at the top for the :class:`persistent.Persistent` class.
+We will use this for a new ``Page`` class in a moment.
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :lines: 1-2
+ :lineno-match:
+ :emphasize-lines: 1
+ :language: py
+
+Then we add a ``Wiki`` class.
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :lines: 4-6
+ :lineno-match:
+ :language: py
+
+We want it to inherit from the :class:`persistent.mapping.PersistentMapping` class because it provides mapping behavior.
+It also makes sure that our ``Wiki`` page is stored as a "first-class" persistent object in our ZODB database.
+
+Our ``Wiki`` class should have two attributes set to ``None`` at class scope: ``__parent__`` and ``__name__``.
+If a model has a ``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid` application, it means that it is the :term:`root` model.
+The ``__name__`` of the root model is also always ``None``.
+
+Now we add a ``Page`` class.
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :lines: 8-10
+ :lineno-match:
+ :language: py
+
+This class should inherit from the :class:`persistent.Persistent` class.
+We will give it an ``__init__`` method that accepts a single parameter named ``data``.
+This parameter will contain the :term:`reStructuredText` body representing the wiki page content.
+
+Note that ``Page`` objects don't have an initial ``__name__`` or ``__parent__`` attribute.
+All objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute.
+We do not specify these here.
+Instead both ``__name__`` and ``__parent__`` will be set by a :term:`view` function when a ``Page`` is added to our ``Wiki`` mapping.
+We will create this function in the next chapter.
+
+As a last step, edit the ``appmaker`` function.
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :lines: 12-20
+ :lineno-match:
+ :emphasize-lines: 4-8
+ :language: py
+
+The :term:`root` :term:`resource` of our application is a Wiki instance.
+
+We will also slot a single page object (the 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.
+
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 (See
-:ref:`wiki-start-the-application`), you'll wind
-up with a Python traceback on your console that ends with this exception:
+We cannot.
+At this point, our system is in a "non-runnable" state
+We will 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 (See :ref:`wiki-start-the-application`), you will 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 d584a1b41..bd8dc6ecf 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -4,44 +4,37 @@
Defining Views
==============
-A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid`
-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.
+A :term:`view callable` in a :term:`traversal`-based :app:`Pyramid` 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
- 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
- :app:`Pyramid` application; the calling conventions can be used
- 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
- 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
- need the ``context`` within a view function that only takes
- the request as a single argument, you can obtain it via
- ``request.context``.
-
-We're going to define several :term:`view callable` functions, then wire them
-into :app:`Pyramid` using some :term:`view configuration`.
+ A :app:`Pyramid` view can also be defined as callable which accepts *only* a :term:`request` argument.
+ You will see this one-argument pattern used in other :app:`Pyramid` tutorials and applications.
+ Either calling convention will work in any :app:`Pyramid` application.
+ The calling conventions can be used interchangeably as necessary.
+
+ In :term:`traversal`-based applications, URLs are mapped to a context :term:`resource`.
+ Since our :term:`resource tree` also represents our application's "domain model", we are 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 need the ``context`` within a view function that only takes the request as a single argument, you can obtain it via ``request.context``.
+
+We will define several :term:`view callable` functions, then wire them into :app:`Pyramid` using some :term:`view configuration`.
+
+.. seealso::
+
+ This chapter will introduce more concepts, as did the previous.
+ See also the chapter :ref:`resources_chapter` for a complete description of resources and the chapter :ref:`traversal_chapter` for the technical details of how traversal works in Pyramid.
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 cookiecutter; it doesn't know
-about our custom application requirements.
+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 cookiecutter.
+It does not 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 ``requires``
-parameter in the ``setup()`` function.
+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:
@@ -52,17 +45,15 @@ Open ``setup.py`` and edit it to look like the following:
Only the highlighted line needs to be added.
+
.. _wiki-running-pip-install:
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.
+Since a new software dependency was added, you 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.
+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:
@@ -78,237 +69,238 @@ On Windows:
cd tutorial
%VENV%\Scripts\pip install -e .
-Success executing this command will end with a line to the console something
-like:
+Success executing this command will end with a line to the console similar to the following:
.. code-block:: text
- Successfully installed docutils-0.13.1 tutorial
+ Successfully installed docutils-0.14 tutorial
-Adding view functions in ``views.py``
-=====================================
+Adding view functions in the ``views`` package
+==============================================
-It's time for a major change. Open ``tutorial/views.py`` and edit it to look
-like the following:
+It is time for a major change.
+Open ``tutorial/views/default.py`` and edit it to look like the following:
-.. literalinclude:: src/views/tutorial/views.py
+.. literalinclude:: src/views/tutorial/views/default.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 originally rendered after we selected the ``zodb`` backend option in the
-cookiecutter. It was only an example and isn't relevant to our application.
+We got rid of the ``my_view`` view function and its decorator that was added when originally rendered after we selected the ``zodb`` backend option in the cookiecutter.
+It was only an example and is not relevant to our application.
-Then we added four :term:`view callable` functions to our ``views.py``
-module:
+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.
+We will 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
- 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.
+ There is nothing special about the filename ``views.py``.
+ A project may have many view callables throughout its codebase in arbitrarily named files.
+ Files that implement view callables often have ``view`` in their names (or may live in a Python subpackage of your application package named ``views``), but this is only by convention.
+
The ``view_wiki`` view function
-------------------------------
Following is the code for the ``view_wiki`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 12-14
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 13-15
:lineno-match:
: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 :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.
+.. note::
+
+ In our code, we use an *import* that is *relative* to our package named ``tutorial``.
+ This means 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.
+ This means 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 this class implement the :class:`pyramid.interfaces.IResponse` interface, similar to :class:`pyramid.response.Response`.
+It uses the :meth:`pyramid.request.Request.route_url` API to construct an URL to the ``FrontPage`` page resource (in other words, ``http://localhost:6543/FrontPage``), and uses it as the ``location`` of the ``HTTPFound`` response, forming an HTTP redirect.
+
The ``view_page`` view function
-------------------------------
Here is the code for the ``view_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 16-33
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 18-35
:lineno-match:
: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
-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.
-
-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 the
-matched WikiWord name, the function generates an "add" link as the
-substitution value and returns it.
-
-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
-it.
-
-The arguments we wrap into a dictionary include ``page``, ``content``, and
-``edit_url``. As a result, the *template* associated with this view callable
-(via ``renderer=`` in its configuration) will be able to use these names to
-perform various rendering tasks. The template associated with this view
-callable will be a template which lives in ``templates/view.pt``.
-
-Note the contrast between this view callable and the ``view_wiki`` view
-callable. In the ``view_wiki`` view callable, we unconditionally return a
-:term:`response` object. In the ``view_page`` view callable, we return a
-*dictionary*. It is *always* fine to return a :term:`response` object from a
-:app:`Pyramid` view. Returning a dictionary is allowed only when there is a
-:term:`renderer` associated with the view callable in the view configuration.
+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 page as HTML.
+The body is stored as the ``data`` attribute of the context passed to the view.
+The context will be a ``Page`` resource.
+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 the matched ``WikiWord`` name, the function generates an "add" link as the substitution value and returns it.
+
+As a result, the ``page_text`` variable is now a fully formed bit of HTML containing various view and add links for ``WikiWord``\s based on the content of our current page resource.
+
+We then generate an edit URL because it is easier to do here than in the template.
+Finally we wrap up a number of arguments in a dictionary and return it.
+
+The arguments we wrap into a dictionary include ``page``, ``page_text``, and ``edit_url``.
+As a result, the *template* associated with this view callable (via ``renderer=`` in its configuration) will be able to use these names to perform various rendering tasks.
+The template associated with this view callable will be a template which lives in ``templates/view.pt``.
+
+Note the contrast between this view callable and the ``view_wiki`` view callable.
+In the ``view_wiki`` view callable, we unconditionally return a :term:`response` object.
+In the ``view_page`` view callable, we return a *dictionary*. It is *always* fine to return a :term:`response` object from a :app:`Pyramid` view.
+Returning a dictionary is allowed only when there is a :term:`renderer` associated with the view callable in the view configuration.
+
The ``add_page`` view function
------------------------------
Here is the code for the ``add_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 35-50
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 38-53
:lineno-match:
: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
-``edit.pt`` instead of ``add.pt``.
-
-The ``add_page`` function will be invoked when a user clicks on a WikiWord
-which isn't yet represented as a page in the system. The ``check`` function
-within the ``view_page`` view generates URLs to this view. It also acts as a
-handler for the form that is generated when we want to add a page resource.
-The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a
-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``,
-the :term:`subpath` will be a tuple: ``('SomeName',)``.
-
-The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name),
-and aliases it to the name attribute in order to know the name of the page
-we're trying to add.
-
-If the view rendering is *not* a result of a form submission (if the
-expression ``'form.submitted' in request.params`` is ``False``), the view
-renders a template. To do so, it generates a "save url" which the template
-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 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
-``view_page`` view (the default view for a page) for the newly created page.
+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, then 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 ``edit.pt`` instead of ``add.pt``.
+
+The ``add_page`` function will be invoked when a user clicks on a ``WikiWord`` that is not yet represented as a page in the system.
+The ``check`` function within the ``view_page`` view generates URLs to this view.
+It also acts as a handler for the form that is generated when we want to add a page resource.
+The ``context`` of the ``add_page`` view is always a ``Wiki`` resource (*not* a ``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, for example, ``http://localhost:6543/add_page/SomeName``, then the :term:`subpath` will be a tuple ``('SomeName',)``.
+
+The add view takes the zero\ :sup:`th` element of the subpath (the wiki page name), then aliases it to the name attribute to know the name of the page we are 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``), then the view renders a template.
+To do so, it generates a ``save_url`` which the template uses as the form post URL during rendering.
+We are lazy here, so we try to use the same template (``templates/edit.pt``) for both the add and edit views.
+To do so, we create a dummy ``Page`` resource object to satisfy the edit form's desire to have *some* page object exposed as ``page``.
+We then set the ``Page`` object's ``__name__`` and ``__parent__``.
+Then we will 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``), then do the following:
+
+- Grab the page body from the form data as ``body``.
+- Create a ``Page`` object using the name in the subpath and the page body as ``page``.
+- Set the ``Page`` object's ``__name__`` and ``__parent__``.
+- Save it into "our context" (the ``Wiki``) using the ``__setitem__`` method of the context.
+- We then redirect back to the ``view_page`` view (the default view for a page) for the newly created page.
+
+.. seealso::
+
+ In the :ref:`previous chapter <wiki_defining_the_domain_model>`, we mentioned that all objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute.
+ That provides location awareness for resources.
+ See also the section on :ref:`location_aware` in the :ref:`resources_chapter` chapter for a complete discussion.
+
The ``edit_page`` view function
-------------------------------
Here is the code for the ``edit_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 52-60
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 56-64
:lineno-match:
: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
-a Page resource is the context, and a :term:`view name` exists as the result
-of traversal named ``edit_page``, this view will be used. We inform
-:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as
-a ``renderer``.
-
-The ``edit_page`` function will be invoked when a user clicks the "Edit this
-Page" button on the view form. It renders an edit form but it also acts as
-the form post view callable for the form it renders. The ``context`` of the
-``edit_page`` view will *always* be a Page resource (never a Wiki resource).
-
-If the view execution is *not* a result of a form submission (if the
-expression ``'form.submitted' in request.params`` is ``False``), the view
-simply renders the edit form, passing the page resource, and a ``save_url``
-which will be used as the action of the generated form.
-
-If the view execution *is* a result of a form submission (if the expression
-``'form.submitted' in request.params`` is ``True``), the view grabs the
-``body`` element of the request parameter and sets it as the ``data``
-attribute of the page context. It then redirects to the default view of the
-context (the page), which will always be the ``view_page`` view.
+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 a ``Page`` resource is the context, and a :term:`view name` exists as the result of traversal named ``edit_page``, this view will be used.
+We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``.
+
+The ``edit_page`` function will be invoked when a user clicks the "Edit this Page" button on the view form.
+It renders an edit form.
+It also acts as the form post view callable for the form it renders.
+The ``context`` of the ``edit_page`` view will *always* be a ``Page`` resource (never a ``Wiki`` resource).
+
+If the view execution is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), then the view renders the edit form, passing the page resource, and a ``save_url`` which will be used as the action of the generated form.
+
+If the view execution *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), the view grabs the ``body`` element of the request parameter and sets it as the ``data`` attribute of the page context.
+It then redirects to the default view of the context (the page), which will always be the ``view_page`` view.
+
+
+Modifying the ``notfound_view`` in ``notfound.py``
+--------------------------------------------------
+
+We have one more view to modify.
+Open ``tutorial/views/notfound.py`` and make the changes shown by the emphasized lines.
+
+.. literalinclude:: src/views/tutorial/views/notfound.py
+ :linenos:
+ :language: python
+ :emphasize-lines: 3-4, 9-12
+
+We need to import the ``Page`` from our models.
+We eventually return a ``Page`` object as ``page`` into the template ``layout.pt`` to display its name in the title tag.
+
Adding templates
================
-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.
+The ``view_page``, ``add_page``, and ``edit_page`` views that we 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.
+
+
+The ``layout.pt`` template
+--------------------------
+
+Update ``tutorial/templates/layout.pt`` with the following content, as indicated by the emphasized lines:
+
+.. literalinclude:: src/views/tutorial/templates/layout.pt
+ :linenos:
+ :emphasize-lines: 11-12, 37-41
+ :language: html
+
+Since we are using a templating engine, we can factor common boilerplate out of our page templates into reusable components.
+We can do this via :term:`METAL` macros and slots.
+
+- The cookiecutter defined a macro named ``layout`` (line 1).
+ This macro consists of the entire template.
+- We changed the ``title`` tag to use the ``name`` attribute of a ``page`` object, or if it does not exist then the page title (lines 11-12).
+- The cookiecutter defined a macro customization point or `slot` (line 36).
+ This slot is inside the macro ``layout``.
+ Therefore it can be replaced by content, customizing the macro.
+- We added a ``div`` element with a link to allow the user to return to the front page (lines 37-41).
+- We removed the row of icons and links from the original cookiecutter.
+
+.. seealso::
+
+ Please refer to the Chameleon documentation for more information about using `METAL <https://chameleon.readthedocs.io/en/latest/>`_ for defining and using macros and slots.
+
The ``view.pt`` template
------------------------
@@ -318,16 +310,18 @@ Rename ``tutorial/templates/mytemplate.pt`` to ``tutorial/templates/view.pt`` an
.. literalinclude:: src/views/tutorial/templates/view.pt
:linenos:
:language: html
- :emphasize-lines: 11-12,37-52
+ :emphasize-lines: 5-16
This template is used by ``view_page()`` for displaying a single
wiki page. It includes:
-- A ``div`` element that is replaced with the ``content`` value provided by
- the view (lines 37-39). ``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 41-43).
+- The use of a macro to load the entire template ``layout.pt``.
+- The template fills the slot named ``content`` (line 2) with a ``div`` element.
+- A ``div`` element that is replaced with the ``page_text`` value provided by the view (line 5).
+ ``page_text`` contains HTML, so the ``structure`` keyword is used to prevent escaping HTML entities, such as changing ``>`` to ``&gt;``.
+- A link that points at the "edit" URL, which invokes the ``edit_page`` view for the page being viewed (lines 9-11).
+- A ``span`` whose content is replaced by the name of the page, if present.
+
The ``edit.pt`` template
------------------------
@@ -337,59 +331,52 @@ Copy ``tutorial/templates/view.pt`` to ``tutorial/templates/edit.pt`` and edit t
.. literalinclude:: src/views/tutorial/templates/edit.pt
:linenos:
:language: html
+ :emphasize-lines: 5-20
-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:
+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:
-- A 10-row by 60-column ``textarea`` field named ``body`` that is filled
- with any existing page data when it is rendered (line 46).
-- A submit button that has the name ``form.submitted`` (line 49).
+- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with any existing page data when it is rendered (lines 11-13).
+- A submit button that has the name ``form.submitted`` (lines 16-18).
-The form POSTs back to the ``save_url`` argument supplied by the view (line
-44). The view will use the ``body`` and ``form.submitted`` values.
+When submitted, the form sends a POST request to the ``save_url`` argument supplied by the view (line 9).
+The view will use the ``body`` and ``form.submitted`` values.
-.. 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.
+.. 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
-------------
-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.
+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 by the cookiecutter at the time we created the project.
-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.
+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 in the ``routes.py`` file.
+Any number and type of static assets can be placed in this directory (or subdirectories)
+They are referred to by either URL or using the convenience method ``static_url``, for example ``request.static_url('<package>:static/foo.css')``, within templates.
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:
+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.
-- http://localhost:6543/ invokes the ``view_wiki`` view. This always
- redirects to the ``view_page`` view of the ``FrontPage`` Page resource.
+- http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front page resource.
+ This is because it is the :term:`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.
+- http://localhost:6543/FrontPage/edit_page invokes the edit view for the ``FrontPage`` ``Page`` resource.
-- http://localhost:6543/FrontPage/edit_page invokes the edit view for the
- ``FrontPage`` Page resource.
+- http://localhost:6543/add_page/SomePageName 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: tuple index out of range`` error.
+ You will see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`.
-- To generate an error, visit http://localhost:6543/add_page which will
- generate an ``IndexError: tuple index out of range`` error. You'll see an
- interactive traceback facility provided by :term:`pyramid_debugtoolbar`.
+- To generate a not found error, visit http://localhost:6543/wakawaka which will invoke the ``notfound_view`` view provided by the cookiecutter.
diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst
index 30d443bb8..5c86293f6 100644
--- a/docs/tutorials/wiki/design.rst
+++ b/docs/tutorials/wiki/design.rst
@@ -4,85 +4,77 @@
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.
+Following is a quick overview of the design of our wiki application to help us understand the changes that we will make 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.
+We choose to use :term:`reStructuredText` markup in the wiki text.
+Conversion 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.
+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.
+They store the text content.
+
+URLs like ``/PageName`` will be traversed using Wiki[ *PageName* ] => page.
+The resulting context is the page resource of an existing page.
-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.
+Its name and reference are added to the Wiki mapping.
-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.
+It will be used as the wiki home page.
-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.
+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.
-As of version 1.5 :app:`Pyramid` no longer ships with templating systems. In this tutorial, we will use :term:`Chameleon`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language.
+As of version 1.5 :app:`Pyramid` no longer ships with templating systems.
+In this tutorial we will use :term:`Chameleon`.
+Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language.
Security
--------
-We'll eventually be adding security to our application. The components we'll
-use to do this are below.
+We'll eventually add 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.
+- 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.
+- 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.
+- ``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`:
+- 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 |
- +----------+----------------+----------------+
+ +----------+----------------+----------------+
+ | 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.
+- 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.
+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:
+The URL, context, actions, template and permission associated to each view are listed in the following table:
+----------------------+-------------+-----------------+-----------------------+------------+------------+
| URL | View | Context | Action | Template | Permission |
@@ -139,10 +131,6 @@ listed in the following table:
| | | | /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.
+.. [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 36d00adb4..c23f79b5a 100644
--- a/docs/tutorials/wiki/distributing.rst
+++ b/docs/tutorials/wiki/distributing.rst
@@ -4,10 +4,8 @@
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 contains the ``tutorial`` package and the
-``setup.py`` file.
+Once your application works properly, you can create a :term:`distribution` from it by using the ``setup.py sdist`` command.
+The following commands assume your current working directory contains the ``tutorial`` package and the ``setup.py`` file.
On Unix:
@@ -31,10 +29,8 @@ The output of such a command will be something like:
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
-<https://pypi.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.
+This command creates a subdirectory named ``dist``.
+Inside that is a tarball named ``tutorial-0.0.tar.gz``, which is the :term:`distribution` of your application.
+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 <https://pypi.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/installation.rst b/docs/tutorials/wiki/installation.rst
index d0037e584..37e3498b2 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -4,12 +4,13 @@
Installation
============
+
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.
+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`.
@@ -17,13 +18,17 @@ install Pyramid**. Thereby you will satisfy the following requirements.
Install cookiecutter
--------------------
-We will use a :term:`cookiecutter` to create a Python package project from a Python package project template. See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions.
+We will use a :term:`cookiecutter` to create a Python package project from a Python package project template.
+See `Cookiecutter Installation <https://cookiecutter.readthedocs.io/en/latest/installation.html>`_ for instructions.
Generate a Pyramid project from a cookiecutter
----------------------------------------------
-We will create a Pyramid project in your home directory for Unix or at the root for Windows. It is assumed you know the path to where you installed ``cookiecutter``. Issue the following commands and override the defaults in the prompts as follows.
+We will create a Pyramid project in your home directory for Unix or at the root for Windows.
+It is assumed you know the path to where you installed ``cookiecutter``.
+Issue the following commands and override the defaults in the prompts as follows.
+
On Unix
^^^^^^^
@@ -33,6 +38,7 @@ On Unix
cd ~
cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master
+
On Windows
^^^^^^^^^^
@@ -41,30 +47,33 @@ On Windows
cd \
cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout master
+
On all operating systems
^^^^^^^^^^^^^^^^^^^^^^^^
If prompted for the first item, accept the default ``yes`` by hitting return.
.. code-block:: text
- You've cloned ~/.cookiecutters/pyramid-cookiecutter-theone before.
- Is it okay to delete and re-clone it? [yes]: yes
+ You've downloaded ~/.cookiecutters/pyramid-cookiecutter-starter before.
+ Is it okay to delete and re-download it? [yes]: yes
project_name [Pyramid Scaffold]: myproj
repo_name [myproj]: tutorial
Select template_language:
1 - jinja2
2 - chameleon
3 - mako
- Choose from 1, 2, 3 [1]: 1
+ Choose from 1, 2, 3 [1]: 2
Select backend:
1 - none
2 - sqlalchemy
3 - zodb
Choose from 1, 2, 3 [1]: 3
+
Change directory into your newly created project
------------------------------------------------
+
On Unix
^^^^^^^
@@ -72,6 +81,7 @@ On Unix
cd tutorial
+
On Windows
^^^^^^^^^^
@@ -85,6 +95,7 @@ Set and use a ``VENV`` environment variable
We will set the ``VENV`` environment variable to the absolute path of the virtual environment, and use it going forward.
+
On Unix
^^^^^^^
@@ -92,6 +103,7 @@ On Unix
export VENV=~/tutorial
+
On Windows
^^^^^^^^^^
@@ -103,6 +115,7 @@ On Windows
Create a virtual environment
----------------------------
+
On Unix
^^^^^^^
@@ -110,17 +123,10 @@ On Unix
python3 -m venv $VENV
+
On Windows
^^^^^^^^^^
-Each version of Python uses different paths, so you might need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher.
-
-Python 2.7:
-
-.. code-block:: doscon
-
- c:\Python27\Scripts\virtualenv %VENV%
-
Python 3.7:
.. code-block:: doscon
@@ -131,6 +137,7 @@ Python 3.7:
Upgrade packaging tools in the virtual environment
--------------------------------------------------
+
On Unix
^^^^^^^
@@ -138,6 +145,7 @@ On Unix
$VENV/bin/pip install --upgrade pip setuptools
+
On Windows
^^^^^^^^^^
@@ -151,7 +159,10 @@ On Windows
Installing the project in development mode
------------------------------------------
-In order to do development on the project easily, you must "register" the project as a development egg in your workspace. We will install testing requirements at the same time. We do so with the following command.
+In order to work on the project, you must "register" the project as a development egg in your workspace.
+We will install testing requirements at the same time.
+We do so with the following command.
+
On Unix
^^^^^^^
@@ -160,6 +171,7 @@ On Unix
$VENV/bin/pip install -e ".[testing]"
+
On Windows
^^^^^^^^^^
@@ -167,6 +179,7 @@ On Windows
%VENV%\Scripts\pip install -e ".[testing]"
+
On all operating systems
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -174,16 +187,18 @@ The console will show ``pip`` checking for packages and installing missing packa
.. code-block:: bash
- Successfully installed BTrees-4.3.1 Chameleon-3.0 Mako-1.0.6 \
- MarkupSafe-0.23 PasteDeploy-1.5.2 Pygments-2.1.3 WebOb-1.6.3 \
- WebTest-2.0.23 ZConfig-3.1.0 ZEO-5.0.4 ZODB-5.1.1 ZODB3-3.11.0 \
- beautifulsoup4-4.5.1 coverage-4.2 mock-2.0.0 pbr-1.10.0 persistent-4.2.2 \
- py-1.4.31 pyramid-1.7.3 pyramid-chameleon-0.3 pyramid-debugtoolbar-3.0.5 \
- pyramid-mako-1.0.2 pyramid-tm-1.1.1 pyramid-zodbconn-0.7 pytest-3.0.5 \
- pytest-cov-2.4.0 repoze.lru-0.6 six-1.10.0 transaction-2.0.3 \
- translationstring-1.3 tutorial venusian-1.0 waitress-1.0.1 \
- zc.lockfile-1.2.1 zdaemon-4.2.0 zodbpickle-0.6.0 zodburi-2.0 \
- zope.deprecation-4.2.0 zope.interface-4.3.3
+ Successfully installed BTrees-4.5.1 Chameleon-3.5 Mako-1.0.7 \
+ MarkupSafe-1.1.0 PasteDeploy-1.5.2 Pygments-2.2.0 WebTest-2.0.32 \
+ ZConfig-3.3.0 ZEO-5.2.0 ZODB-5.5.1 ZODB3-3.11.0 atomicwrites-1.2.1 \
+ attrs-18.2.0 beautifulsoup4-4.6.3 coverage-4.5.2 hupper-1.4.1 \
+ more-itertools-4.3.0 persistent-4.4.3 plaster-1.0 plaster-pastedeploy-0.6 \
+ pluggy-0.8.0 py-1.7.0 pyramid-1.10.1 pyramid-chameleon-0.3 \
+ pyramid-debugtoolbar-4.5 pyramid-mako-1.0.2 pyramid-retry-1.0 \
+ pyramid-tm-2.2.1 pyramid-zodbconn-0.8.1 pytest-4.0.0 pytest-cov-2.6.0 \
+ repoze.lru-0.7 six-1.11.0 transaction-2.4.0 translationstring-1.3 \
+ tutorial venusian-1.1.0 waitress-1.1.0 webob-1.8.4 zc.lockfile-1.4 \
+ zdaemon-4.3 zodbpickle-1.0.2 zodburi-2.3.0 zope.deprecation-4.3.0 \
+ zope.interface-4.6.0
Testing requirements are defined in our project's ``setup.py`` file, in the ``tests_require`` and ``extras_require`` stanzas.
@@ -208,6 +223,7 @@ requirements, you may run the tests for the project. The following commands
provide options to ``pytest`` that specify the module for which its tests shall be
run, and to run ``pytest`` in quiet mode.
+
On Unix
^^^^^^^
@@ -215,6 +231,7 @@ On Unix
$VENV/bin/pytest -q
+
On Windows
^^^^^^^^^^
@@ -233,13 +250,11 @@ For a successful test run, you should see output that ends like this:
Expose test coverage information
--------------------------------
-You can run the ``pytest`` command to see test coverage information. This
-runs the tests in the same way that ``pytest`` does, but provides additional
-:term:`coverage` information, exposing which lines of your project are covered by the
-tests.
+You can run the ``pytest`` command to see test coverage information.
+This runs the tests in the same way that ``pytest`` does, but provides additional :term:`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.
-We've already installed the ``pytest-cov`` package into our virtual
-environment, so we can run the tests with coverage.
On Unix
^^^^^^^
@@ -248,6 +263,7 @@ On Unix
$VENV/bin/pytest --cov --cov-report=term-missing
+
On Windows
^^^^^^^^^^
@@ -259,23 +275,30 @@ If successful, you will see output something like this:
.. code-block:: bash
- ======================== test session starts ========================
- platform Python 3.6.0, pytest-3.0.5, py-1.4.31, pluggy-0.4.0
- rootdir: /Users/stevepiercy/tutorial, inifile:
- plugins: cov-2.4.0
- collected 1 items
+ ======================== test session starts =========================
+ platform darwin -- Python 3.7.0, pytest-4.0.0, py-1.7.0, pluggy-0.8.0
+ rootdir: /Users/stevepiercy/projects/hack-on-pyramid/tutorial, inifile: pytest.ini
+ plugins: cov-2.6.0
+ collected 1 item
tutorial/tests.py .
- ------------------ coverage: platform Python 3.6.0 ------------------
- Name Stmts Miss Cover Missing
- -------------------------------------------------------
- tutorial/__init__.py 14 9 36% 7-8, 14-20
- tutorial/models.py 10 6 40% 9-14
- tutorial/views.py 4 0 100%
- -------------------------------------------------------
- TOTAL 28 15 46%
+ [100%]
+
+ ---------- coverage: platform darwin, python 3.7.0-final-0 -----------
+ Name Stmts Miss Cover Missing
+ -----------------------------------------------------------
+ tutorial/__init__.py 17 12 29% 7-8, 14-23
+ tutorial/models/__init__.py 8 4 50% 9-12
+ tutorial/pshell.py 6 6 0% 1-12
+ tutorial/routes.py 2 2 0% 1-2
+ tutorial/views/__init__.py 0 0 100%
+ tutorial/views/default.py 4 0 100%
+ tutorial/views/notfound.py 4 4 0% 1-7
+ -----------------------------------------------------------
+ TOTAL 41 28 32%
+
- ===================== 1 passed in 0.31 seconds ======================
+ ===================== 1 passed in 0.31 seconds =======================
Our package doesn't quite have 100% test coverage.
@@ -285,11 +308,10 @@ Our package doesn't quite have 100% test coverage.
Test and coverage cookiecutter defaults
---------------------------------------
-The Pyramid cookiecutter includes configuration defaults for ``pytest`` and
-test coverage. These configuration files are ``pytest.ini`` and
-``.coveragerc``, located at the root of your package. Without these defaults,
-we would need to specify the path to the module on which we want to run tests
-and coverage.
+The Pyramid cookiecutter includes configuration defaults for ``pytest`` and test coverage.
+These configuration files are ``pytest.ini`` and ``.coveragerc``, located at the root of your package.
+Without these defaults, we would need to specify the path to the module on which we want to run tests and coverage.
+
On Unix
^^^^^^^
@@ -305,13 +327,11 @@ On Windows
%VENV%\Scripts\pytest --cov=tutorial tutorial\tests.py -q
-``pytest`` follows :ref:`conventions for Python test discovery
-<pytest:test discovery>`, and the configuration defaults from the cookiecutter
-tell ``pytest`` where to find the module on which we want to run tests and
-coverage.
-.. seealso:: See ``pytest``'s documentation for :ref:`pytest:usage` or invoke
- ``pytest -h`` to see its full set of options.
+``pytest`` follows :ref:`conventions for Python test discovery <pytest:test discovery>`.
+The configuration defaults from the cookiecutter tell ``pytest`` where to find the module on which we want to run tests and coverage.
+
+.. seealso:: See ``pytest``'s documentation for :ref:`pytest:usage` or invoke ``pytest -h`` to see its full set of options.
.. _wiki-start-the-application:
@@ -319,8 +339,9 @@ coverage.
Start the application
---------------------
-Start the application. See :ref:`what_is_this_pserve_thing` for more
-information on ``pserve``.
+Start the application.
+See :ref:`what_is_this_pserve_thing` for more information on ``pserve``.
+
On Unix
^^^^^^^
@@ -329,6 +350,7 @@ On Unix
$VENV/bin/pserve development.ini --reload
+
On Windows
^^^^^^^^^^
@@ -336,10 +358,10 @@ On Windows
%VENV%\Scripts\pserve development.ini --reload
+
.. note::
- Your OS firewall, if any, may pop up a dialog asking for authorization
- to allow python to accept incoming network connections.
+ Your OS firewall, if any, may pop up a dialog asking for authorization to allow python to accept incoming network connections.
If successful, you will see something like this on your console:
@@ -356,13 +378,12 @@ This means the server is ready to accept requests.
Visit the application in a browser
----------------------------------
-In a browser, visit http://localhost:6543/. You will see the generated
-application's default page.
+In a browser, visit http://localhost:6543/.
+You will see the generated application's default page.
-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.
+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.
Decisions the cookiecutter backend option ``zodb`` has made for you
@@ -371,27 +392,11 @@ Decisions the cookiecutter backend option ``zodb`` has made for you
When creating a project and selecting the backend option of ``zodb``, the cookiecutter makes the following assumptions:
- You are willing to use :term:`ZODB` for persistent storage.
-
- You are willing to use :term:`traversal` to map URLs to code.
-
-- You want to use pyramid_zodbconn_, pyramid_tm_, and the transaction_ packages
- to manage connections and transactions with :term:`ZODB`.
+- You want to use `pyramid_zodbconn <https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/>`_, `pyramid_tm <https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/>`_, and the `transaction <https://zodb.readthedocs.io/en/latest/transactions.html>`_ packages to manage connections and transactions with :term:`ZODB`.
.. note::
- :app:`Pyramid` supports any persistent storage mechanism (e.g., an 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 :term:`traversal` and :term:`ZODB`.
-
-.. _pyramid_chameleon:
- https://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
-
-.. _pyramid_tm:
- https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/
-
-.. _pyramid_zodbconn:
- https://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/
-
-.. _transaction:
- https://zodb.readthedocs.io/en/latest/transactions.html
+ :app:`Pyramid` supports any persistent storage mechanism (e.g., an 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 will only use :term:`traversal` and :term:`ZODB`.
diff --git a/docs/tutorials/wiki/src/authorization/MANIFEST.in b/docs/tutorials/wiki/src/authorization/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/authorization/MANIFEST.in
+++ b/docs/tutorials/wiki/src/authorization/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/authorization/pytest.ini b/docs/tutorials/wiki/src/authorization/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/authorization/pytest.ini
+++ b/docs/tutorials/wiki/src/authorization/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py
index 7011387f6..fa5948acb 100644
--- a/docs/tutorials/wiki/src/authorization/setup.py
+++ b/docs/tutorials/wiki/src/authorization/setup.py
@@ -10,22 +10,22 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
'docutils',
'bcrypt',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
index 58635ea74..935a5d6d2 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py
@@ -15,18 +15,17 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
with Configurator(settings=settings) as config:
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py
index ebd70e912..ebd70e912 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/pshell.py b/docs/tutorials/wiki/src/authorization/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/routes.py b/docs/tutorials/wiki/src/authorization/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
index eedb83da4..6438b1569 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt
@@ -1,72 +1,27 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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">
+ <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">
+ </p>
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </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>
+ <textarea tal:content="page.data"
+ class="form-control" name="body"
+ rows="10" cols="60"></textarea>
</div>
<div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ <button type="submit"
+ name="form.submitted" value="Save"
+ class="btn btn-default">Save</button>
</div>
- </form>
- </div>
- </div>
+ </form>
</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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt
new file mode 100644
index 000000000..b606e8dad
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/layout.pt
@@ -0,0 +1,59 @@
+<!DOCTYPE html metal:define-macro="layout">
+<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><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 metal:define-slot="content">No content</div>
+ <div class="content">
+ <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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></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 626db6637..acc4876cf 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt
@@ -1,39 +1,7 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Login - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 class="content">
<p>
<strong>
Login
@@ -54,22 +22,7 @@
<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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
index f2a9249ef..911ab0c99 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt
@@ -1,72 +1,23 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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">
+ <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>
+ </p>
+ <div tal:replace="structure page_text">
+ 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>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
</div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py
index ea2da01af..3a3b170e2 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views/default.py
@@ -2,30 +2,29 @@ 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,
+ remember,
+)
+from pyramid.view import (
+ forbidden_view_config,
+ view_config,
)
-
-from .security import USERS, check_password
-from .models import Page
+from ..models import Page
+from ..security import check_password, USERS
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='.models.Wiki',
+
+@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',
+
+@view_config(context='..models.Page', renderer='../templates/view.pt',
permission='view')
def view_page(context, request):
wiki = context.__parent__
@@ -40,14 +39,15 @@ def view_page(context, request):
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)
+ page_text = publish_parts(context.data, writer_name='html')['html_body']
+ page_text = wikiwords.sub(check, page_text)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page=context, content=content, edit_url=edit_url,
+ return dict(page=context, page_text=page_text, edit_url=edit_url,
logged_in=request.authenticated_userid)
-@view_config(name='add_page', context='.models.Wiki',
- renderer='templates/edit.pt',
+
+@view_config(name='add_page', context='..models.Wiki',
+ renderer='../templates/edit.pt',
permission='edit')
def add_page(context, request):
pagename = request.subpath[0]
@@ -65,8 +65,9 @@ def add_page(context, request):
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',
+
+@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:
@@ -77,9 +78,10 @@ def edit_page(context, request):
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')
+
+@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
@@ -104,10 +106,11 @@ def login(request):
came_from=came_from,
login=login,
password=password,
+ title='Login',
)
-@view_config(context='.models.Wiki', name='logout')
+@view_config(context='..models.Wiki', name='logout')
def logout(request):
headers = forget(request)
return HTTPFound(location=request.resource_url(request.context),
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py b/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py
new file mode 100644
index 000000000..d44b4d0e6
--- /dev/null
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views/notfound.py
@@ -0,0 +1,12 @@
+from pyramid.view import notfound_view_config
+
+from ..models import Page
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ pagename = request.path
+ page = Page(pagename)
+ page.__name__ = pagename
+ return dict(page=page)
diff --git a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in
+++ b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/basiclayout/pytest.ini b/docs/tutorials/wiki/src/basiclayout/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/basiclayout/pytest.ini
+++ b/docs/tutorials/wiki/src/basiclayout/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py
index e05e279e2..d6d488ed2 100644
--- a/docs/tutorials/wiki/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki/src/basiclayout/setup.py
@@ -10,20 +10,20 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
index f2b3c9568..830a607f3 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py
@@ -11,13 +11,12 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
with Configurator(settings=settings) as config:
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py
index aca6a4129..aca6a4129 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py b/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py b/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt
new file mode 100644
index 000000000..9fdaef00f
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/layout.pt
@@ -0,0 +1,62 @@
+<!DOCTYPE html metal:define-macro="layout">
+<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>Cookiecutter Starter project for the Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 metal:define-slot="content">No content</div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <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="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
index d63ea8c45..adac4fe35 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
@@ -1,65 +1,11 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 Project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
- </div>
- </div>
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid
+ application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
- <div class="row">
- <div class="links">
- <ul>
- <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="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
deleted file mode 100644
index c1878bdd0..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pyramid.view import view_config
-from .models import MyModel
-
-
-@view_config(context=MyModel, renderer='templates/mytemplate.pt')
-def my_view(request):
- return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py
new file mode 100644
index 000000000..5d708d15c
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/default.py
@@ -0,0 +1,8 @@
+from pyramid.view import view_config
+
+from ..models import MyModel
+
+
+@view_config(context=MyModel, renderer='../templates/mytemplate.pt')
+def my_view(request):
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py
new file mode 100644
index 000000000..728791d0a
--- /dev/null
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki/src/installation/MANIFEST.in b/docs/tutorials/wiki/src/installation/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/installation/MANIFEST.in
+++ b/docs/tutorials/wiki/src/installation/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/installation/pytest.ini b/docs/tutorials/wiki/src/installation/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/installation/pytest.ini
+++ b/docs/tutorials/wiki/src/installation/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py
index e05e279e2..d6d488ed2 100644
--- a/docs/tutorials/wiki/src/installation/setup.py
+++ b/docs/tutorials/wiki/src/installation/setup.py
@@ -10,20 +10,20 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
index f2b3c9568..830a607f3 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
@@ -11,13 +11,12 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
with Configurator(settings=settings) as config:
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/installation/tutorial/models.py b/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py
index aca6a4129..aca6a4129 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/models.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/installation/tutorial/pshell.py b/docs/tutorials/wiki/src/installation/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/installation/tutorial/routes.py b/docs/tutorials/wiki/src/installation/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt
new file mode 100644
index 000000000..9fdaef00f
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/layout.pt
@@ -0,0 +1,62 @@
+<!DOCTYPE html metal:define-macro="layout">
+<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>Cookiecutter Starter project for the Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 metal:define-slot="content">No content</div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <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="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
index d63ea8c45..adac4fe35 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
@@ -1,65 +1,11 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 Project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
- </div>
- </div>
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid
+ application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
- <div class="row">
- <div class="links">
- <ul>
- <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="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/tests.py b/docs/tutorials/wiki/src/installation/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/installation/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/installation/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views.py b/docs/tutorials/wiki/src/installation/tutorial/views.py
deleted file mode 100644
index c1878bdd0..000000000
--- a/docs/tutorials/wiki/src/installation/tutorial/views.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pyramid.view import view_config
-from .models import MyModel
-
-
-@view_config(context=MyModel, renderer='templates/mytemplate.pt')
-def my_view(request):
- return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/default.py b/docs/tutorials/wiki/src/installation/tutorial/views/default.py
new file mode 100644
index 000000000..5d708d15c
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/views/default.py
@@ -0,0 +1,8 @@
+from pyramid.view import view_config
+
+from ..models import MyModel
+
+
+@view_config(context=MyModel, renderer='../templates/mytemplate.pt')
+def my_view(request):
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py b/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py
new file mode 100644
index 000000000..728791d0a
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki/src/models/MANIFEST.in b/docs/tutorials/wiki/src/models/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/models/MANIFEST.in
+++ b/docs/tutorials/wiki/src/models/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/models/pytest.ini b/docs/tutorials/wiki/src/models/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/models/pytest.ini
+++ b/docs/tutorials/wiki/src/models/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py
index e05e279e2..d6d488ed2 100644
--- a/docs/tutorials/wiki/src/models/setup.py
+++ b/docs/tutorials/wiki/src/models/setup.py
@@ -10,20 +10,20 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py
index f2b3c9568..830a607f3 100644
--- a/docs/tutorials/wiki/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py
@@ -11,13 +11,12 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
with Configurator(settings=settings) as config:
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/models/tutorial/models.py b/docs/tutorials/wiki/src/models/tutorial/models/__init__.py
index 7c6597afa..7c6597afa 100644
--- a/docs/tutorials/wiki/src/models/tutorial/models.py
+++ b/docs/tutorials/wiki/src/models/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/models/tutorial/pshell.py b/docs/tutorials/wiki/src/models/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/models/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/models/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/models/tutorial/routes.py b/docs/tutorials/wiki/src/models/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/theme.css b/docs/tutorials/wiki/src/models/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/models/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/models/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/404.pt b/docs/tutorials/wiki/src/models/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt
new file mode 100644
index 000000000..9fdaef00f
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/layout.pt
@@ -0,0 +1,62 @@
+<!DOCTYPE html metal:define-macro="layout">
+<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>Cookiecutter Starter project for the Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 metal:define-slot="content">No content</div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <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="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
index d63ea8c45..adac4fe35 100644
--- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
@@ -1,65 +1,11 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Cookiecutter ZODB project for the Pyramid Web Framework</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 Project</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
- </div>
- </div>
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, a&nbsp;Pyramid
+ application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
- <div class="row">
- <div class="links">
- <ul>
- <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="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/models/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/models/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py
deleted file mode 100644
index c1878bdd0..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/views.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from pyramid.view import view_config
-from .models import MyModel
-
-
-@view_config(context=MyModel, renderer='templates/mytemplate.pt')
-def my_view(request):
- return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/models/tutorial/views/__init__.py b/docs/tutorials/wiki/src/models/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/models/tutorial/views/default.py b/docs/tutorials/wiki/src/models/tutorial/views/default.py
new file mode 100644
index 000000000..5d708d15c
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/views/default.py
@@ -0,0 +1,8 @@
+from pyramid.view import view_config
+
+from ..models import MyModel
+
+
+@view_config(context=MyModel, renderer='../templates/mytemplate.pt')
+def my_view(request):
+ return {'project': 'myproj'}
diff --git a/docs/tutorials/wiki/src/models/tutorial/views/notfound.py b/docs/tutorials/wiki/src/models/tutorial/views/notfound.py
new file mode 100644
index 000000000..728791d0a
--- /dev/null
+++ b/docs/tutorials/wiki/src/models/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki/src/tests/MANIFEST.in b/docs/tutorials/wiki/src/tests/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/tests/MANIFEST.in
+++ b/docs/tutorials/wiki/src/tests/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/tests/pytest.ini b/docs/tutorials/wiki/src/tests/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/tests/pytest.ini
+++ b/docs/tutorials/wiki/src/tests/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py
index 7011387f6..fa5948acb 100644
--- a/docs/tutorials/wiki/src/tests/setup.py
+++ b/docs/tutorials/wiki/src/tests/setup.py
@@ -10,22 +10,22 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
'docutils',
'bcrypt',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
index 58635ea74..935a5d6d2 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -15,18 +15,17 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
with Configurator(settings=settings) as config:
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py
index ebd70e912..ebd70e912 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/models.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/tests/tutorial/pshell.py b/docs/tutorials/wiki/src/tests/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/tests/tutorial/routes.py b/docs/tutorials/wiki/src/tests/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/tests/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
index eedb83da4..6438b1569 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt
@@ -1,72 +1,27 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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">
+ <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">
+ </p>
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </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>
+ <textarea tal:content="page.data"
+ class="form-control" name="body"
+ rows="10" cols="60"></textarea>
</div>
<div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ <button type="submit"
+ name="form.submitted" value="Save"
+ class="btn btn-default">Save</button>
</div>
- </form>
- </div>
- </div>
+ </form>
</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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt
new file mode 100644
index 000000000..b606e8dad
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/layout.pt
@@ -0,0 +1,59 @@
+<!DOCTYPE html metal:define-macro="layout">
+<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><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 metal:define-slot="content">No content</div>
+ <div class="content">
+ <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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></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
index 626db6637..acc4876cf 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt
@@ -1,39 +1,7 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>Login - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 class="content">
<p>
<strong>
Login
@@ -54,22 +22,7 @@
<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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
index f2a9249ef..911ab0c99 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt
@@ -1,72 +1,23 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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">
+ <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>
+ </p>
+ <div tal:replace="structure page_text">
+ 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>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
</div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py
index 098e9c1bd..ff1c07b7c 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py
@@ -8,12 +8,12 @@ class PageModelTests(unittest.TestCase):
from .models import Page
return Page
- def _makeOne(self, data=u'some data'):
+ def _makeOne(self, data='some data'):
return self._getTargetClass()(data=data)
def test_constructor(self):
instance = self._makeOne()
- self.assertEqual(instance.data, u'some data')
+ self.assertEqual(instance.data, 'some data')
class WikiModelTests(unittest.TestCase):
@@ -43,7 +43,7 @@ class AppmakerTests(unittest.TestCase):
class ViewWikiTests(unittest.TestCase):
def test_it(self):
- from .views import view_wiki
+ from .views.default import view_wiki
context = testing.DummyResource()
request = testing.DummyRequest()
response = view_wiki(context, request)
@@ -51,7 +51,7 @@ class ViewWikiTests(unittest.TestCase):
class ViewPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from .views import view_page
+ from .views.default import view_page
return view_page(context, request)
def test_it(self):
@@ -64,7 +64,7 @@ class ViewPageTests(unittest.TestCase):
info = self._callFUT(context, request)
self.assertEqual(info['page'], context)
self.assertEqual(
- info['content'],
+ info['page_text'],
'<div class="document">\n'
'<p>Hello <a href="http://example.com/add_page/CruelWorld">'
'CruelWorld</a> '
@@ -77,7 +77,7 @@ class ViewPageTests(unittest.TestCase):
class AddPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from .views import add_page
+ from .views.default import add_page
return add_page(context, request)
def test_it_notsubmitted(self):
@@ -103,7 +103,7 @@ class AddPageTests(unittest.TestCase):
class EditPageTests(unittest.TestCase):
def _callFUT(self, context, request):
- from .views import edit_page
+ from .views.default import edit_page
return edit_page(context, request)
def test_it_notsubmitted(self):
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views/default.py
index ea2da01af..3a3b170e2 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/views/default.py
@@ -2,30 +2,29 @@ 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,
+ remember,
+)
+from pyramid.view import (
+ forbidden_view_config,
+ view_config,
)
-
-from .security import USERS, check_password
-from .models import Page
+from ..models import Page
+from ..security import check_password, USERS
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(context='.models.Wiki',
+
+@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',
+
+@view_config(context='..models.Page', renderer='../templates/view.pt',
permission='view')
def view_page(context, request):
wiki = context.__parent__
@@ -40,14 +39,15 @@ def view_page(context, request):
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)
+ page_text = publish_parts(context.data, writer_name='html')['html_body']
+ page_text = wikiwords.sub(check, page_text)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page=context, content=content, edit_url=edit_url,
+ return dict(page=context, page_text=page_text, edit_url=edit_url,
logged_in=request.authenticated_userid)
-@view_config(name='add_page', context='.models.Wiki',
- renderer='templates/edit.pt',
+
+@view_config(name='add_page', context='..models.Wiki',
+ renderer='../templates/edit.pt',
permission='edit')
def add_page(context, request):
pagename = request.subpath[0]
@@ -65,8 +65,9 @@ def add_page(context, request):
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',
+
+@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:
@@ -77,9 +78,10 @@ def edit_page(context, request):
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')
+
+@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
@@ -104,10 +106,11 @@ def login(request):
came_from=came_from,
login=login,
password=password,
+ title='Login',
)
-@view_config(context='.models.Wiki', name='logout')
+@view_config(context='..models.Wiki', name='logout')
def logout(request):
headers = forget(request)
return HTTPFound(location=request.resource_url(request.context),
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py b/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py
new file mode 100644
index 000000000..d44b4d0e6
--- /dev/null
+++ b/docs/tutorials/wiki/src/tests/tutorial/views/notfound.py
@@ -0,0 +1,12 @@
+from pyramid.view import notfound_view_config
+
+from ..models import Page
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ pagename = request.path
+ page = Page(pagename)
+ page.__name__ = pagename
+ return dict(page=page)
diff --git a/docs/tutorials/wiki/src/views/MANIFEST.in b/docs/tutorials/wiki/src/views/MANIFEST.in
index 81beba1b1..05cc195d9 100644
--- a/docs/tutorials/wiki/src/views/MANIFEST.in
+++ b/docs/tutorials/wiki/src/views/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.jinja2
diff --git a/docs/tutorials/wiki/src/views/pytest.ini b/docs/tutorials/wiki/src/views/pytest.ini
index 8b76bc410..a3489cdf8 100644
--- a/docs/tutorials/wiki/src/views/pytest.ini
+++ b/docs/tutorials/wiki/src/views/pytest.ini
@@ -1,3 +1,3 @@
[pytest]
testpaths = tutorial
-python_files = *.py
+python_files = test*.py
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index a11ae6c8f..6f3cae397 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -10,21 +10,21 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'plaster_pastedeploy',
- 'pyramid >= 1.9a',
+ 'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
+ 'waitress',
'pyramid_retry',
'pyramid_tm',
'pyramid_zodbconn',
'transaction',
'ZODB3',
- 'waitress',
'docutils',
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py
index f2b3c9568..830a607f3 100644
--- a/docs/tutorials/wiki/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py
@@ -11,13 +11,12 @@ def root_factory(request):
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- settings['tm.manager_hook'] = 'pyramid_tm.explicit_manager'
with Configurator(settings=settings) as config:
- config.include('pyramid_chameleon')
config.include('pyramid_tm')
config.include('pyramid_retry')
config.include('pyramid_zodbconn')
config.set_root_factory(root_factory)
- config.add_static_view('static', 'static', cache_max_age=3600)
+ config.include('pyramid_chameleon')
+ config.include('.routes')
config.scan()
- return config.make_wsgi_app()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/views/tutorial/models.py b/docs/tutorials/wiki/src/views/tutorial/models/__init__.py
index 7c6597afa..7c6597afa 100644
--- a/docs/tutorials/wiki/src/views/tutorial/models.py
+++ b/docs/tutorials/wiki/src/views/tutorial/models/__init__.py
diff --git a/docs/tutorials/wiki/src/views/tutorial/pshell.py b/docs/tutorials/wiki/src/views/tutorial/pshell.py
index 3d026291b..a7cfa6a27 100644
--- a/docs/tutorials/wiki/src/views/tutorial/pshell.py
+++ b/docs/tutorials/wiki/src/views/tutorial/pshell.py
@@ -1,5 +1,6 @@
from . import models
+
def setup(env):
request = env['request']
diff --git a/docs/tutorials/wiki/src/views/tutorial/routes.py b/docs/tutorials/wiki/src/views/tutorial/routes.py
new file mode 100644
index 000000000..3c0a37992
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/routes.py
@@ -0,0 +1,2 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/theme.css b/docs/tutorials/wiki/src/views/tutorial/static/theme.css
index 0f4b1a4d4..a70ee557a 100644
--- a/docs/tutorials/wiki/src/views/tutorial/static/theme.css
+++ b/docs/tutorials/wiki/src/views/tutorial/static/theme.css
@@ -17,6 +17,9 @@ h6 {
p {
font-weight: 300;
}
+button, input, optgroup, select, textarea {
+ color: black;
+}
.font-normal {
font-weight: 400;
}
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/404.pt b/docs/tutorials/wiki/src/views/tutorial/templates/404.pt
new file mode 100644
index 000000000..07298940c
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/404.pt
@@ -0,0 +1,10 @@
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
+
+ <div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+ </div>
+
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
index 2db3ca79c..488e7a6af 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt
@@ -1,69 +1,24 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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="content">
+ <p>
+ Editing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </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>
+ <textarea tal:content="page.data"
+ class="form-control" name="body"
+ rows="10" cols="60"></textarea>
</div>
<div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+ <button type="submit"
+ name="form.submitted" value="Save"
+ class="btn btn-default">Save</button>
</div>
- </form>
- </div>
- </div>
+ </form>
</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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt
new file mode 100644
index 000000000..b606e8dad
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/layout.pt
@@ -0,0 +1,59 @@
+<!DOCTYPE html metal:define-macro="layout">
+<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><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on
+ TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+
+ <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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 metal:define-slot="content">No content</div>
+ <div class="content">
+ <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="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
+ <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></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 1feeab5ef..b8a6fbbaf 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
@@ -1,70 +1,20 @@
-<!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')}">
+<div metal:use-macro="load: layout.pt">
+ <div metal:fill-slot="content">
- <title>${page.__name__} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shiv 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" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></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>
+ <div class="content">
+ <div tal:replace="structure page_text">
+ 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>
+ </p>
+ <p>
+ Viewing <strong><span tal:replace="page.__name__">
+ Page Name Goes Here</span></strong>
+ </p>
</div>
- </div>
- </div>
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
- <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
- </body>
-</html>
+ </div>
+</div>
diff --git a/docs/tutorials/wiki/src/views/tutorial/tests.py b/docs/tutorials/wiki/src/views/tutorial/tests.py
index ca7a47279..6279d9f66 100644
--- a/docs/tutorials/wiki/src/views/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/views/tutorial/tests.py
@@ -11,7 +11,8 @@ class ViewTests(unittest.TestCase):
testing.tearDown()
def test_my_view(self):
- from .views import my_view
+ from .views.default import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'myproj')
+
diff --git a/docs/tutorials/wiki/src/views/tutorial/views/__init__.py b/docs/tutorials/wiki/src/views/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views/default.py
index fd2b0edc1..b4b65a49b 100644
--- a/docs/tutorials/wiki/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki/src/views/tutorial/views/default.py
@@ -4,16 +4,18 @@ import re
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
-from .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='.models.Wiki')
+
+@view_config(context='..models.Wiki')
def view_wiki(context, request):
return HTTPFound(location=request.resource_url(context, 'FrontPage'))
-@view_config(context='.models.Page', renderer='templates/view.pt')
+
+@view_config(context='..models.Page', renderer='../templates/view.pt')
def view_page(context, request):
wiki = context.__parent__
@@ -27,13 +29,14 @@ def view_page(context, request):
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)
+ page_text = publish_parts(context.data, writer_name='html')['html_body']
+ page_text = wikiwords.sub(check, page_text)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page=context, content=content, edit_url=edit_url)
+ return dict(page=context, page_text=page_text, edit_url=edit_url)
-@view_config(name='add_page', context='.models.Wiki',
- renderer='templates/edit.pt')
+
+@view_config(name='add_page', context='..models.Wiki',
+ renderer='../templates/edit.pt')
def add_page(context, request):
pagename = request.subpath[0]
if 'form.submitted' in request.params:
@@ -49,12 +52,13 @@ def add_page(context, request):
page.__parent__ = context
return dict(page=page, save_url=save_url)
-@view_config(name='edit_page', context='.models.Page',
- renderer='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=request.resource_url(context))
return dict(page=context,
- save_url=request.resource_url(context, 'edit_page')) \ No newline at end of file
+ save_url=request.resource_url(context, 'edit_page'))
diff --git a/docs/tutorials/wiki/src/views/tutorial/views/notfound.py b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py
new file mode 100644
index 000000000..d44b4d0e6
--- /dev/null
+++ b/docs/tutorials/wiki/src/views/tutorial/views/notfound.py
@@ -0,0 +1,12 @@
+from pyramid.view import notfound_view_config
+
+from ..models import Page
+
+
+@notfound_view_config(renderer='../templates/404.pt')
+def notfound_view(request):
+ request.response.status = 404
+ pagename = request.path
+ page = Page(pagename)
+ page.__name__ = pagename
+ return dict(page=page)
diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst
index fdd218add..a0872e605 100644
--- a/docs/tutorials/wiki/tests.rst
+++ b/docs/tutorials/wiki/tests.rst
@@ -4,58 +4,56 @@
Adding Tests
============
-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.
+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.
+
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 will write a separate test class for each ``model`` class
+We will also write a test class for the ``appmaker``.
+
+To do so, we will retain the ``tutorial.tests.ViewTests`` class that was generated from choosing the ``zodb`` backend option.
+We will add three test classes, one for each of the following:
+
+- the ``Page`` model named ``PageModelTests``
+- the ``Wiki`` model named ``WikiModelTests``
+- the appmaker named ``AppmakerTests``
-To do so, we'll retain the ``tutorial.tests.ViewTests`` class that was
-generated from choosing the ``zodb`` backend option. 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``.
Test the views
==============
-We'll modify our ``tests.py`` file, adding tests for each view function we
-added previously. As a result, we'll delete the ``ViewTests`` class that the
-``zodb`` backend option 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.
+We will modify our ``tests.py`` file, adding tests for each view function that we added previously.
+As a result, we will delete the ``ViewTests`` class that the ``zodb`` backend option 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'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.
+We will test the whole application, covering security aspects that are not tested in the unit tests, such as logging in, logging out, checking that the ``viewer`` user cannot add or edit pages, but the ``editor`` user can, and so on.
+
View the results of all our edits to ``tests.py``
=================================================
-Open the ``tutorial/tests.py`` module, and edit it such that it appears as
-follows:
+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
=================
-We can run these tests by using ``pytest`` similarly to how we did in
-:ref:`running_tests`. Courtesy of the cookiecutter, our testing dependencies have
-already been satisfied and ``pytest`` and coverage have already been
-configured, so we can jump right to running tests.
+We can run these tests by using ``pytest`` similarly to how we did in :ref:`running_tests`.
+Courtesy of the cookiecutter, our testing dependencies have already been satisfied.
+``pytest`` and coverage have already been configured.
+We can jump right to running tests.
On Unix:
@@ -75,3 +73,6 @@ The expected result should look like the following:
.........................
25 passed in 6.87 seconds
+
+If you use Python 3.7, you may see deprecation warnings from the docutils 0.14 package.
+You can apply a [patch](https://sourceforge.net/p/docutils/patches/144/) to fix the issue, or ignore it and wait for the next release of docutils.
diff --git a/docs/tutorials/wiki2/background.rst b/docs/tutorials/wiki2/background.rst
index c14d3cb7d..09315a77d 100644
--- a/docs/tutorials/wiki2/background.rst
+++ b/docs/tutorials/wiki2/background.rst
@@ -17,6 +17,6 @@ variant, etc.) *or* a Windows system of any kind.
.. note::
- This tutorial runs on both Python 2 and 3 without modification.
+ This tutorial runs on Python 3 without modification.
Have fun!
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index 924927cd4..705979065 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -126,16 +126,6 @@ On Unix
On Windows
^^^^^^^^^^
-Each version of Python uses different paths, so you will need to adjust the path to the command for your Python version. Recent versions of the Python 3 installer for Windows now install a Python launcher.
-
-Python 2.7:
-
-.. code-block:: doscon
-
- c:\Python27\Scripts\virtualenv %VENV%
-
-Python 3.7:
-
.. code-block:: doscon
python -m venv %VENV%
diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py
index e2a30c0e7..f71998afc 100644
--- a/docs/tutorials/wiki2/src/authentication/setup.py
+++ b/docs/tutorials/wiki2/src/authentication/setup.py
@@ -25,8 +25,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
index 8ed90d5b2..2f0210255 100644
--- a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
import re
from docutils.core import publish_parts
diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py
index e2a30c0e7..f71998afc 100644
--- a/docs/tutorials/wiki2/src/authorization/setup.py
+++ b/docs/tutorials/wiki2/src/authorization/setup.py
@@ -25,8 +25,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
index ad271fb46..ad8491b7b 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
import re
from docutils.core import publish_parts
diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py
index 11725dd51..746012a74 100644
--- a/docs/tutorials/wiki2/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -23,8 +23,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py
index 11725dd51..746012a74 100644
--- a/docs/tutorials/wiki2/src/installation/setup.py
+++ b/docs/tutorials/wiki2/src/installation/setup.py
@@ -23,8 +23,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py
index 09e3126ea..b9dc9d93f 100644
--- a/docs/tutorials/wiki2/src/models/setup.py
+++ b/docs/tutorials/wiki2/src/models/setup.py
@@ -24,8 +24,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index e2a30c0e7..f71998afc 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -25,8 +25,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
index ad271fb46..ad8491b7b 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
import re
from docutils.core import publish_parts
diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py
index e2a30c0e7..f71998afc 100644
--- a/docs/tutorials/wiki2/src/views/setup.py
+++ b/docs/tutorials/wiki2/src/views/setup.py
@@ -25,8 +25,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/default.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
index a866af1de..5e28b64fd 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
import re
from docutils.core import publish_parts