summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorSteve Piercy <web@stevepiercy.com>2018-11-26 23:59:40 -0800
committerSteve Piercy <web@stevepiercy.com>2018-11-26 23:59:40 -0800
commit2615104ce4ba383a46df3c27ba26cfb86654e116 (patch)
treead938e23efd1be67821ddfb710748e746c92c420 /docs
parent28f24e7592fc5a7fd28874e9a350f80674583471 (diff)
parent587fe72fae0efda3a860d37a1ea2449a41dab622 (diff)
downloadpyramid-2615104ce4ba383a46df3c27ba26cfb86654e116.tar.gz
pyramid-2615104ce4ba383a46df3c27ba26cfb86654e116.tar.bz2
pyramid-2615104ce4ba383a46df3c27ba26cfb86654e116.zip
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'docs')
-rw-r--r--docs/api/compat.rst156
-rw-r--r--docs/conf.py20
-rw-r--r--docs/glossary.rst31
-rw-r--r--docs/narr/commandline.rst8
-rw-r--r--docs/narr/hooks.rst8
-rw-r--r--docs/narr/i18n.rst17
-rw-r--r--docs/narr/install.rst3
-rw-r--r--docs/narr/logging.rst23
-rw-r--r--docs/narr/myproject/setup.py4
-rw-r--r--docs/narr/renderers.rst5
-rw-r--r--docs/narr/startup.rst29
-rw-r--r--docs/narr/traversal.rst8
-rw-r--r--docs/narr/upgrading.rst1
-rw-r--r--docs/narr/urldispatch.rst38
-rw-r--r--docs/narr/views.rst39
-rw-r--r--docs/narr/webob.rst21
-rw-r--r--docs/quick_tour.rst15
-rw-r--r--docs/quick_tour/logging/setup.py4
-rw-r--r--docs/quick_tour/package/setup.py4
-rw-r--r--docs/quick_tour/sessions/setup.py4
-rw-r--r--docs/quick_tour/sqla_demo/setup.py4
-rw-r--r--docs/quick_tour/views/views.py4
-rw-r--r--docs/quick_tutorial/cookiecutters/setup.py4
-rw-r--r--docs/quick_tutorial/index.rst3
-rw-r--r--docs/quick_tutorial/requirements.rst12
-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
145 files changed, 1897 insertions, 2051 deletions
diff --git a/docs/api/compat.rst b/docs/api/compat.rst
deleted file mode 100644
index bb34f38e4..000000000
--- a/docs/api/compat.rst
+++ /dev/null
@@ -1,156 +0,0 @@
-.. _compat_module:
-
-:mod:`pyramid.compat`
-----------------------
-
-The ``pyramid.compat`` module provides platform and version compatibility for
-Pyramid and its add-ons across Python platform and version differences. APIs
-will be removed from this module over time as Pyramid ceases to support
-systems which require compatibility imports.
-
-.. automodule:: pyramid.compat
-
- .. autofunction:: ascii_native_
-
- .. attribute:: binary_type
-
- Binary type for this platform. For Python 3, it's ``bytes``. For
- Python 2, it's ``str``.
-
- .. autofunction:: bytes_
-
- .. attribute:: class_types
-
- Sequence of class types for this platform. For Python 3, it's
- ``(type,)``. For Python 2, it's ``(type, types.ClassType)``.
-
- .. attribute:: configparser
-
- On Python 2, the ``ConfigParser`` module, on Python 3, the
- ``configparser`` module.
-
- .. function:: escape(v)
-
- On Python 2, the ``cgi.escape`` function, on Python 3, the
- ``html.escape`` function.
-
- .. function:: exec_(code, globs=None, locs=None)
-
- Exec code in a compatible way on both Python 2 and 3.
-
- .. attribute:: im_func
-
- On Python 2, the string value ``im_func``, on Python 3, the string
- value ``__func__``.
-
- .. function:: input_(v)
-
- On Python 2, the ``raw_input`` function, on Python 3, the
- ``input`` function.
-
- .. attribute:: integer_types
-
- Sequence of integer types for this platform. For Python 3, it's
- ``(int,)``. For Python 2, it's ``(int, long)``.
-
- .. function:: is_nonstr_iter(v)
-
- Return ``True`` if ``v`` is a non-``str`` iterable on both Python 2 and
- Python 3.
-
- .. function:: iteritems_(d)
-
- Return ``d.items()`` on Python 3, ``d.iteritems()`` on Python 2.
-
- .. function:: itervalues_(d)
-
- Return ``d.values()`` on Python 3, ``d.itervalues()`` on Python 2.
-
- .. function:: iterkeys_(d)
-
- Return ``d.keys()`` on Python 3, ``d.iterkeys()`` on Python 2.
-
- .. attribute:: long
-
- Long type for this platform. For Python 3, it's ``int``. For
- Python 2, it's ``long``.
-
- .. function:: map_(v)
-
- Return ``list(map(v))`` on Python 3, ``map(v)`` on Python 2.
-
- .. attribute:: pickle
-
- ``cPickle`` module if it exists, ``pickle`` module otherwise.
-
- .. attribute:: PY3
-
- ``True`` if running on Python 3, ``False`` otherwise.
-
- .. attribute:: PYPY
-
- ``True`` if running on PyPy, ``False`` otherwise.
-
- .. function:: reraise(tp, value, tb=None)
-
- Reraise an exception in a compatible way on both Python 2 and Python 3,
- e.g. ``reraise(*sys.exc_info())``.
-
- .. attribute:: string_types
-
- Sequence of string types for this platform. For Python 3, it's
- ``(str,)``. For Python 2, it's ``(basestring,)``.
-
- .. attribute:: SimpleCookie
-
- On Python 2, the ``Cookie.SimpleCookie`` class, on Python 3, the
- ``http.cookies.SimpleCookie`` module.
-
- .. autofunction:: text_
-
- .. attribute:: text_type
-
- Text type for this platform. For Python 3, it's ``str``. For Python
- 2, it's ``unicode``.
-
- .. autofunction:: native_
-
- .. attribute:: urlparse
-
- ``urlparse`` module on Python 2, ``urllib.parse`` module on Python 3.
-
- .. attribute:: url_quote
-
- ``urllib.quote`` function on Python 2, ``urllib.parse.quote`` function
- on Python 3.
-
- .. attribute:: url_quote_plus
-
- ``urllib.quote_plus`` function on Python 2, ``urllib.parse.quote_plus``
- function on Python 3.
-
- .. attribute:: url_unquote
-
- ``urllib.unquote`` function on Python 2, ``urllib.parse.unquote``
- function on Python 3.
-
- .. attribute:: url_encode
-
- ``urllib.urlencode`` function on Python 2, ``urllib.parse.urlencode``
- function on Python 3.
-
- .. attribute:: url_open
-
- ``urllib2.urlopen`` function on Python 2, ``urllib.request.urlopen``
- function on Python 3.
-
- .. function:: url_unquote_text(v, encoding='utf-8', errors='replace')
-
- On Python 2, return ``url_unquote(v).decode(encoding(encoding, errors))``;
- on Python 3, return the result of ``urllib.parse.unquote``.
-
- .. function:: url_unquote_native(v, encoding='utf-8', errors='replace')
-
- On Python 2, return ``native_(url_unquote_text_v, encoding, errors))``;
- on Python 3, return the result of ``urllib.parse.unquote``.
-
diff --git a/docs/conf.py b/docs/conf.py
index 15cffb1eb..3b7eb261a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -61,26 +61,26 @@ extensions = [
# Looks for objects in external projects
intersphinx_mapping = {
- 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest', None),
+ 'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None),
'cookbook': ('https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None),
'cookiecutter': ('https://cookiecutter.readthedocs.io/en/latest/', None),
- 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest', None),
+ 'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None),
'jinja2': ('https://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None),
'plaster': ('https://docs.pylonsproject.org/projects/plaster/en/latest/', None),
'pylonswebframework': ('https://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None),
- 'python': ('https://docs.python.org/3', None),
+ 'python': ('https://docs.python.org/3/', None),
'pytest': ('https://docs.pytest.org/en/latest/', None),
- 'sphinx': ('http://www.sphinx-doc.org/en/latest', None),
- 'sqla': ('https://docs.sqlalchemy.org/en/latest', None),
+ 'sphinx': ('http://www.sphinx-doc.org/en/latest/', None),
+ 'sqla': ('https://docs.sqlalchemy.org/en/latest/', None),
'tm': ('https://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None),
- 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None),
- 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest', None),
+ 'toolbar': ('https://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest/', None),
+ 'tstring': ('https://docs.pylonsproject.org/projects/translationstring/en/latest/', None),
'tutorials': ('https://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None),
- 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest', None),
+ 'venusian': ('https://docs.pylonsproject.org/projects/venusian/en/latest/', None),
'webob': ('https://docs.pylonsproject.org/projects/webob/en/latest/', None),
'webtest': ('https://docs.pylonsproject.org/projects/webtest/en/latest/', None),
- 'who': ('https://repozewho.readthedocs.io/en/latest', None),
- 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None),
+ 'who': ('https://repozewho.readthedocs.io/en/latest/', None),
+ 'zcml': ('https://docs.pylonsproject.org/projects/pyramid-zcml/en/latest/', None),
'zcomponent': ('https://zopecomponent.readthedocs.io/en/latest/', None),
'zinterface': ('https://zopeinterface.readthedocs.io/en/latest/', None),
}
diff --git a/docs/glossary.rst b/docs/glossary.rst
index 7f396dc7d..9c6027209 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -165,7 +165,7 @@ Glossary
An object representing a node in the :term:`resource tree` of an
application. If :term:`traversal` is used, a resource is an element in
the resource tree traversed by the system. When traversal is used, a
- resource becomes the :term:`context` of a :term:`view`. If :term:`url
+ resource becomes the :term:`context` of a :term:`view`. If :term:`URL
dispatch` is used, a single resource is generated for each request and
is used as the context resource of a view.
@@ -232,7 +232,7 @@ Glossary
object *location-aware*.
permission
- A string or Unicode object that represents an action being taken against
+ A string that represents an action being taken against
a :term:`context` resource. A permission is associated with a view name
and a resource type by the developer. Resources are decorated with
security declarations (e.g. an :term:`ACL`), which reference these
@@ -289,7 +289,7 @@ Glossary
:term:`authorization policy`.
principal
- A *principal* is a string or Unicode object representing an entity,
+ A *principal* is a string representing an entity,
typically a user or group. Principals are provided by an
:term:`authentication policy`. For example, if a user has the
:term:`userid` `bob`, and is a member of two groups named `group foo` and
@@ -298,7 +298,7 @@ Glossary
foo` and `group bar`.
userid
- A *userid* is a string or Unicode object used to identify and authenticate
+ A *userid* is a string used to identify and authenticate
a real-world user or client. A userid is supplied to an
:term:`authentication policy` in order to discover the user's
:term:`principals <principal>`. In the authentication policies which
@@ -405,13 +405,13 @@ Glossary
the Routes syntax (which was inspired by Ruby On Rails pattern syntax).
route
- A single pattern matched by the :term:`url dispatch` subsystem,
+ A single pattern matched by the :term:`URL dispatch` subsystem,
which generally resolves to a :term:`root factory` (and then
ultimately a :term:`view`).
.. seealso::
- See also :term:`url dispatch`.
+ See also :term:`URL dispatch`.
route configuration
Route configuration is the act of associating request parameters with a
@@ -523,8 +523,8 @@ Glossary
from the :term:`physical root`. For example, the physical path of the
``abc`` subobject of the physical root object is ``/abc``. Physical paths
can also be specified as tuples where the first element is the empty
- string (representing the root), and every other element is a Unicode
- object, e.g. ``('', 'abc')``. Physical paths are also sometimes called
+ string (representing the root), and every other element is a Unicode string,
+ e.g. ``('', 'abc')``. Physical paths are also sometimes called
"traversal paths".
lineage
@@ -755,7 +755,7 @@ Glossary
Translation String
An instance of :class:`pyramid.i18n.TranslationString`, which
- is a class that behaves like a Unicode string, but has several
+ is a class that behaves like a string, but has several
extra attributes such as ``domain``, ``msgid``, and ``mapping``
for use during translation. Translation strings are usually
created by hand within software, but are sometimes created on the
@@ -779,7 +779,7 @@ Glossary
Translator
A callable which receives a :term:`translation string` and returns a
- translated Unicode object for the purposes of internationalization. A
+ translated string for the purposes of internationalization. A
:term:`localizer` supplies a translator to a :app:`Pyramid` application
accessible via its :class:`~pyramid.i18n.Localizer.translate` method.
@@ -912,7 +912,7 @@ Glossary
:meth:`pyramid.config.Configurator.add_route` and
:meth:`pyramid.config.Configurator.add_view` to make it more convenient
to register a collection of views as a single class when using
- :term:`url dispatch`. View handlers ship as part of the
+ :term:`URL dispatch`. View handlers ship as part of the
:term:`pyramid_handlers` add-on package.
Deployment settings
@@ -1071,7 +1071,7 @@ Glossary
Green Unicorn
Aka ``gunicorn``, a fast :term:`WSGI` server that runs on Unix under
- Python 2.6+ or Python 3.1+. See https://gunicorn.org/ for detailed
+ Python 2.6+ or Python 3.4+. See https://gunicorn.org/ for detailed
information.
predicate factory
@@ -1219,3 +1219,10 @@ Glossary
Settings are collected at application startup.
They can affect all the components which make up the the application.
Pyramid itself, any tweens or Pyramid add-ons used, and :ref:`your own code may reference <deployment_settings>` and act on settings.
+
+ constructor
+ A function returning a Pyramid :term:`WSGI` application.
+ Every Pyramid application has a single constructor function named ``main``.
+ It returns a Pyramid :term:`router` generated by a :term:`configurator`, and is written by you.
+ The Pyramid constructor is the application's :term:`entry point`.
+
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index b571a7d7b..21b2a0839 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -1006,8 +1006,8 @@ top-level directory, your ``setup.py`` file will look something like this:
requires = ['pyramid', 'pyramid_debugtoolbar']
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
@@ -1073,8 +1073,8 @@ The result will be something like:
requires = ['pyramid', 'pyramid_debugtoolbar']
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest', # includes virtualenv
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 9f405c336..0dac8d426 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -659,15 +659,15 @@ that implements the following interface:
``virtual_root``, and ``virtual_root_path``. These values are
typically the result of a resource tree traversal. ``root``
is the physical root object, ``context`` will be a resource
- object, ``view_name`` will be the view name used (a Unicode
- name), ``subpath`` will be a sequence of Unicode names that
+ object, ``view_name`` will be the view name used (a string),
+ ``subpath`` will be a sequence of strings that
followed the view name but were not traversed, ``traversed``
- will be a sequence of Unicode names that were traversed
+ will be a sequence of strings that were traversed
(including the virtual root path, if any) ``virtual_root``
will be a resource object representing the virtual root (or the
physical root if traversal was not performed), and
``virtual_root_path`` will be a sequence representing the
- virtual root path (a sequence of Unicode names) or None if
+ virtual root path (a sequence of strings) or ``None`` if
traversal was not performed.
Extra keys for special purpose functionality can be added as
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index 9b838c7f4..b8cd396c0 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -33,7 +33,7 @@ While you write your software, you can insert specialized markup into your
Python code that makes it possible for the system to translate text values into
the languages used by your application's users. This markup creates a
:term:`translation string`. A translation string is an object that behaves
-mostly like a normal Unicode object, except that it also carries around extra
+mostly like a normal Unicode string, except that it also carries around extra
information related to its job as part of the :app:`Pyramid` translation
machinery.
@@ -49,7 +49,7 @@ The most primitive way to create a translation string is to use the
from pyramid.i18n import TranslationString
ts = TranslationString('Add')
-This creates a Unicode-like object that is a TranslationString.
+This creates a ``str``-like object that is a TranslationString.
.. note::
@@ -61,9 +61,8 @@ This creates a Unicode-like object that is a TranslationString.
The first argument to :class:`~pyramid.i18n.TranslationString` is the
``msgid``; it is required. It represents the key into the translation mappings
-provided by a particular localization. The ``msgid`` argument must be a Unicode
-object or an ASCII string. The msgid may optionally contain *replacement
-markers*. For instance:
+provided by a particular localization. The ``msgid`` argument must be a string.
+The ``msgid`` may optionally contain *replacement markers*. For instance:
.. code-block:: python
:linenos:
@@ -81,14 +80,14 @@ may be supplied at the same time as the replacement marker itself:
from pyramid.i18n import TranslationString
ts = TranslationString('Add ${number}', mapping={'number':1})
-Any number of replacement markers can be present in the msgid value, any number
+Any number of replacement markers can be present in the ``msgid`` value, any number
of times. Only markers which can be replaced by the values in the *mapping*
will be replaced at translation time. The others will not be interpolated and
will be output literally.
A translation string should also usually carry a *domain*. The domain
represents a translation category to disambiguate it from other translations of
-the same msgid, in case they conflict.
+the same ``msgid``, in case they conflict.
.. code-block:: python
:linenos:
@@ -100,7 +99,7 @@ the same msgid, in case they conflict.
The above translation string named a domain of ``form``. A :term:`translator`
function will often use the domain to locate the right translator file on the
filesystem which contains translations for a given domain. In this case, if it
-were trying to translate our msgid to German, it might try to find a
+were trying to translate our ``msgid`` to German, it might try to find a
translation from a :term:`gettext` file within a :term:`translation directory`
like this one:
@@ -429,7 +428,7 @@ Performing a Translation
A :term:`localizer` has a ``translate`` method which accepts either a
:term:`translation string` or a Unicode string and which returns a Unicode
-object representing the translation. Generating a translation in a view
+string representing the translation. Generating a translation in a view
component of an application might look like so:
.. code-block:: python
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index 248b432d3..268ae5f8d 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -21,8 +21,7 @@ the following sections.
.. sidebar:: Python Versions
- As of this writing, :app:`Pyramid` is tested against Python 2.7,
- Python 3.4, Python 3.5, Python 3.6, Python 3.7, and PyPy.
+ As of this writing, :app:`Pyramid` is tested against Python 3.4, Python 3.5, Python 3.6, Python 3.7, Python 3.8 (with allowed failures), and PyPy3.
:app:`Pyramid` is known to run on all popular Unix-like systems such as Linux,
macOS, and FreeBSD, as well as on Windows platforms. It is also known to
diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst
index 2acdb33f4..58bd2d4ec 100644
--- a/docs/narr/logging.rst
+++ b/docs/narr/logging.rst
@@ -37,15 +37,20 @@ These ``.ini`` file sections are passed to the `logging module's config file con
PasteDeploy ``.ini`` files use the Python standard library :mod:`ConfigParser
format <ConfigParser>`. This is the same format used as the Python
:ref:`logging module's Configuration file format <logging-config-fileformat>`.
-The application-related and logging-related sections in the configuration file
-can coexist peacefully, and the logging-related sections in the file are used
-from when you run ``pserve``.
-
-The ``pserve`` command calls the :func:`pyramid.paster.setup_logging` function,
-a thin wrapper around the :func:`logging.config.fileConfig` using the specified
-``.ini`` file, if it contains a ``[loggers]`` section (all of the
-cookiecutter-generated ``.ini`` files do). ``setup_logging`` reads the logging
-configuration from the ini file upon which ``pserve`` was invoked.
+The application-related and logging-related sections in the configuration file can coexist peacefully.
+The logging-related sections in the file configure logging when you run ``pserve``.
+
+.. index::
+ pair: logging; startup
+
+If the configuration ``.ini`` file, specified when invoking ``pserve``, contains a ``[loggers]`` section then on :ref:`startup <the_startup_process>` the following process takes place:
+
+#. The ``pserve`` command calls the :func:`pyramid.paster.setup_logging` function, passing the ``.ini`` file.
+
+#. ``setup_logging`` is a thin wrapper which calls the Python standard library's :func:`logging.config.fileConfig`.
+
+#. :func:`logging.config.fileConfig` reads the logging configuration from the ``.ini`` file and configures logging.
+
Default logging configuration is provided in both the default
``development.ini`` and the ``production.ini`` files. If you use our cookiecutter to generate a Pyramid project with the name of the package as ``hello_world``, then the logging configuration
diff --git a/docs/narr/myproject/setup.py b/docs/narr/myproject/setup.py
index cf626880f..1ee272270 100644
--- a/docs/narr/myproject/setup.py
+++ b/docs/narr/myproject/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index 493f808d5..6b4982e4b 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -145,8 +145,7 @@ used in the ``renderer`` attribute of view configurations.
The ``string`` renderer renders a view callable result to a string. If a view
callable returns a non-Response object, and the ``string`` renderer is
associated in that view's configuration, the result will be to run the object
-through the Python ``str`` function to generate a string. Note that if a
-Unicode object is returned by the view callable, it is not ``str()``-ified.
+through the Python ``str`` function to generate a string.
Here's an example of a view that returns a dictionary. If the ``string``
renderer is specified in the configuration for this view, the view will render
@@ -496,7 +495,7 @@ interface. A typical class that follows this setup is as follows:
def __call__(self, value, system):
""" Call the renderer implementation with the value
and the system value passed in as arguments and return
- the result (a string or unicode object). The value is
+ the result (a bytes or string object). The value is
the return value of a view. The system value is a
dictionary containing available system values
(e.g., view, context, and request). """
diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst
index 17926c716..37825d2da 100644
--- a/docs/narr/startup.rst
+++ b/docs/narr/startup.rst
@@ -47,10 +47,16 @@ Here's a high-level time-ordered overview of what happens when you press
the :term:`PasteDeploy` library and returns a parser that can understand
the format.
+ .. _ini_section_discovery:
+
#. The :term:`PasteDeploy` finds a section named either ``[app:main]``,
- ``[pipeline:main]``, or ``[composite:main]`` in the ``.ini`` file. This
- section represents the configuration of a :term:`WSGI` application that will
- be served. If you're using a simple application (e.g., ``[app:main]``), the
+ ``[pipeline:main]``, or ``[composite:main]`` in the ``.ini`` file.
+ This section configures the :term:`WSGI` webserver which serves your application.
+ As such it is the ``ini`` section for your application and can be the source for many of your application's :term:`settings`.
+
+ .. _entry_point_discovery:
+
+ If you're using a simple application (e.g., ``[app:main]``), the
application's ``paste.app_factory`` :term:`entry point` will be named on the
``use=`` line within the section's configuration. If instead of a simple
application, you're using a WSGI :term:`pipeline` (e.g., a
@@ -63,17 +69,20 @@ Here's a high-level time-ordered overview of what happens when you press
will have a single ``[app:main]`` section in it, and this will be the
application served.
+ .. index::
+ pair: logging; startup
+
+ .. _startup_logging_initialization:
+
#. The framework finds all :mod:`logging` related configuration in the ``.ini``
file and uses it to configure the Python standard library logging system for
- this application. See :ref:`logging_config` for more information.
+ the application. See :ref:`logging_config` for more information.
-#. The application's *constructor* named by the entry point referenced on the
- ``use=`` line of the section representing your :app:`Pyramid` application is
- passed the key/value parameters mentioned within the section in which it's
- defined. The constructor is meant to return a :term:`router` instance,
- which is a :term:`WSGI` application.
+#. The application's entry point, usually the entry point referenced on the :ref:`above mentioned <entry_point_discovery>` ``use=`` line, is the application's :term:`constructor`.
+ It is passed the key/value parameters in :ref:`the application's .ini section <ini_section_discovery>`.
+ The constructor should return a :term:`router` instance, which is a :term:`WSGI` application.
- For :app:`Pyramid` applications, the constructor will be a function named
+ For :app:`Pyramid` applications, the constructor is a function named
``main`` in the ``__init__.py`` file within the :term:`package` in which
your application lives. If this function succeeds, it will return a
:app:`Pyramid` :term:`router` instance. Here's the contents of an example
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
index 9b91e21ba..0282c6096 100644
--- a/docs/narr/traversal.rst
+++ b/docs/narr/traversal.rst
@@ -237,19 +237,19 @@ uses this algorithm to find a :term:`context` resource and a :term:`view name`.
The traversal algorithm by default attempts to first URL-unquote and then
Unicode-decode each path segment derived from ``PATH_INFO`` from its
- natural byte string (``str`` type) representation. URL unquoting is
+ natural string representation. URL unquoting is
performed using the Python standard library ``urllib.unquote`` function.
Conversion from a URL-decoded string into Unicode is attempted using the
UTF-8 encoding. If any URL-unquoted path segment in ``PATH_INFO`` is not
decodeable using the UTF-8 decoding, a :exc:`TypeError` is raised. A
- segment will be fully URL-unquoted and UTF8-decoded before it is passed in
+ segment will be fully URL-unquoted and UTF-8-decoded before it is passed in
to the ``__getitem__`` of any resource during traversal.
Thus a request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the
- traversal sequence ``[u'a', u'b', u'c']``.
+ traversal sequence ``['a', 'b', 'c']``.
#. :term:`Traversal` begins at the root resource returned by the root factory.
- For the traversal sequence ``[u'a', u'b', u'c']``, the root resource's
+ For the traversal sequence ``['a', 'b', 'c']``, the root resource's
``__getitem__`` is called with the name ``'a'``. Traversal continues
through the sequence. In our example, if the root resource's
``__getitem__`` called with the name ``a`` returns a resource (a.k.a.
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
index 12e146cf1..87e4647c3 100644
--- a/docs/narr/upgrading.rst
+++ b/docs/narr/upgrading.rst
@@ -86,7 +86,6 @@ At the time of a Pyramid version release, each supports all versions of Python
through the end of their lifespans. The end-of-life for a given version of
Python is when security updates are no longer released.
-- `Python 2.7 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2020-01-01.
- `Python 3.4 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2019-03-16 .
- `Python 3.5 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2020-09-13 .
- `Python 3.6 Lifespan <https://devguide.python.org/#status-of-python-branches>`_ 2021-12-23.
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index 3b737b46d..9372163e8 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -165,8 +165,8 @@ The above pattern will match these URLs, generating the following matchdicts:
.. code-block:: text
- foo/1/2 -> {'baz':u'1', 'bar':u'2'}
- foo/abc/def -> {'baz':u'abc', 'bar':u'def'}
+ foo/1/2 -> {'baz': '1', 'bar': '2'}
+ foo/abc/def -> {'baz': 'abc', 'bar': 'def'}
It will not match the following patterns however:
@@ -184,7 +184,7 @@ instance, if this route pattern was used:
foo/{name}.html
The literal path ``/foo/biz.html`` will match the above route pattern, and the
-match result will be ``{'name':u'biz'}``. However, the literal path
+match result will be ``{'name': 'biz'}``. However, the literal path
``/foo/biz`` will not match, because it does not contain a literal ``.html`` at
the end of the segment represented by ``{name}.html`` (it only contains
``biz``, not ``biz.html``).
@@ -242,7 +242,7 @@ The matchdict will look like so (the value is URL-decoded / UTF-8 decoded):
.. code-block:: text
- {'bar':u'La Pe\xf1a'}
+ {'bar': 'La Pe\xf1a'}
Literal strings in the path segment should represent the *decoded* value of the
``PATH_INFO`` provided to Pyramid. You don't want to use a URL-encoded value
@@ -303,10 +303,10 @@ The above pattern will match these URLs, generating the following matchdicts:
.. code-block:: text
foo/1/2/ ->
- {'baz':u'1', 'bar':u'2', 'fizzle':()}
+ {'baz': '1', 'bar': '2', 'fizzle': ()}
foo/abc/def/a/b/c ->
- {'baz':u'abc', 'bar':u'def', 'fizzle':(u'a', u'b', u'c')}
+ {'baz': 'abc', 'bar': 'def', 'fizzle': ('a', 'b', 'c')}
Note that when a ``*stararg`` remainder match is matched, the value put into
the matchdict is turned into a tuple of path segments representing the
@@ -327,7 +327,7 @@ Will generate the following matchdict:
.. code-block:: text
- {'fizzle':(u'La Pe\xf1a', u'a', u'b', u'c')}
+ {'fizzle': ('La Pe\xf1a', 'a', 'b', 'c')}
By default, the ``*stararg`` will parse the remainder sections into a tuple
split by segment. Changing the regular expression used to match a marker can
@@ -341,8 +341,8 @@ The above pattern will match these URLs, generating the following matchdicts:
.. code-block:: text
- foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':u''}
- foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c'}
+ foo/1/2/ -> {'baz': '1', 'bar': '2', 'fizzle': ''}
+ foo/abc/def/a/b/c -> {'baz': 'abc', 'bar': 'def', 'fizzle': 'a/b/c'}
This occurs because the default regular expression for a marker is ``[^/]+``
which will match everything up to the first ``/``, while ``{fizzle:.*}`` will
@@ -513,7 +513,7 @@ When the ``/site/{id}`` route pattern matches during a request, the
When this route matches, a ``matchdict`` will be generated and attached to the
request as ``request.matchdict``. If the specific URL matched is ``/site/1``,
the ``matchdict`` will be a dictionary with a single key, ``id``; the value
-will be the string ``'1'``, ex.: ``{'id':'1'}``.
+will be the string ``'1'``, ex.: ``{'id': '1'}``.
The ``mypackage.views`` module referred to above might look like so:
@@ -562,12 +562,12 @@ Here is an example of a corresponding ``mypackage.views`` module:
@view_config(route_name='user')
def user_view(request):
user = request.matchdict['user']
- return Response(u'The user is {}.'.format(user))
+ return Response('The user is {}.'.format(user))
@view_config(route_name='tag')
def tag_view(request):
tag = request.matchdict['tag']
- return Response(u'The tag is {}.'.format(tag))
+ return Response('The tag is {}.'.format(tag))
The above configuration will allow :app:`Pyramid` to service URLs in these
forms:
@@ -581,17 +581,17 @@ forms:
- When a URL matches the pattern ``/ideas/{idea}``, the view callable
available at the dotted Python pathname ``mypackage.views.idea_view`` will
be called. For the specific URL ``/ideas/1``, the ``matchdict`` generated
- and attached to the :term:`request` will consist of ``{'idea':'1'}``.
+ and attached to the :term:`request` will consist of ``{'idea': '1'}``.
- When a URL matches the pattern ``/users/{user}``, the view callable
available at the dotted Python pathname ``mypackage.views.user_view`` will be
called. For the specific URL ``/users/1``, the ``matchdict`` generated and
- attached to the :term:`request` will consist of ``{'user':'1'}``.
+ attached to the :term:`request` will consist of ``{'user': '1'}``.
- When a URL matches the pattern ``/tags/{tag}``, the view callable available
at the dotted Python pathname ``mypackage.views.tag_view`` will be called.
For the specific URL ``/tags/1``, the ``matchdict`` generated and attached to
- the :term:`request` will consist of ``{'tag':'1'}``.
+ the :term:`request` will consist of ``{'tag': '1'}``.
In this example we've again associated each of our routes with a :term:`view
callable` directly. In all cases, the request, which will have a ``matchdict``
@@ -714,13 +714,13 @@ Therefore, if you've added a route like so:
.. code-block:: python
- config.add_route('la', u'/La Peña/{city}')
+ config.add_route('la', '/La Peña/{city}')
And you later generate a URL using ``route_path`` or ``route_url`` like so:
.. code-block:: python
- url = request.route_path('la', city=u'Québec')
+ url = request.route_path('la', city='Québec')
You will wind up with the path encoded to UTF-8 and URL-quoted like so:
@@ -739,7 +739,7 @@ And you later generate a URL using ``route_path`` or ``route_url`` using a
.. code-block:: python
- url = request.route_path('abc', foo=u'Québec/biz')
+ url = request.route_path('abc', foo='Québec/biz')
The value you pass will be URL-quoted except for embedded slashes in the
result:
@@ -752,7 +752,7 @@ You can get a similar result by passing a tuple composed of path elements:
.. code-block:: python
- url = request.route_path('abc', foo=(u'Québec', u'biz'))
+ url = request.route_path('abc', foo=('Québec', 'biz'))
Each value in the tuple will be URL-quoted and joined by slashes in this case:
diff --git a/docs/narr/views.rst b/docs/narr/views.rst
index a53063f78..1b4118b85 100644
--- a/docs/narr/views.rst
+++ b/docs/narr/views.rst
@@ -442,10 +442,7 @@ browser client, and its ``action`` points at some :app:`Pyramid` view code:
:linenos:
<html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- </head>
- <form method="POST" action="myview">
+ <form method="POST" action="myview" accept-charset="UTF-8">
<div>
<input type="text" name="firstname"/>
</div>
@@ -457,8 +454,8 @@ browser client, and its ``action`` points at some :app:`Pyramid` view code:
</html>
The ``myview`` view code in the :app:`Pyramid` application *must* expect that
-the values returned by ``request.params`` will be of type ``unicode``, as
-opposed to type ``str``. The following will work to accept a form post from the
+the values returned by ``request.params`` will be of type ``str``, as opposed
+to type ``bytes``. The following will work to accept a form post from the
above form:
.. code-block:: python
@@ -468,24 +465,12 @@ above form:
firstname = request.params['firstname']
lastname = request.params['lastname']
-But the following ``myview`` view code *may not* work, as it tries to decode
-already-decoded (``unicode``) values obtained from ``request.params``:
-
-.. code-block:: python
- :linenos:
-
- def myview(request):
- # the .decode('utf-8') will break below if there are any high-order
- # characters in the firstname or lastname
- firstname = request.params['firstname'].decode('utf-8')
- lastname = request.params['lastname'].decode('utf-8')
-
For implicit decoding to work reliably, you should ensure that every form you
render that posts to a :app:`Pyramid` view explicitly defines a charset
encoding of UTF-8. This can be done via a response that has a
``;charset=UTF-8`` in its ``Content-Type`` header; or, as in the form above,
-with a ``meta http-equiv`` tag that implies that the charset is UTF-8 within
-the HTML ``head`` of the page containing the form. This must be done
+with an ``accept-charset`` attribute, informing the browser that the
+server expects the form content to be encoded using UTF-8. This must be done
explicitly because all known browser clients assume that they should encode
form data in the same character set implied by the ``Content-Type`` value of
the response containing the form when subsequently submitting that form. There
@@ -499,21 +484,15 @@ when it can't decode some high-order character encoded in another character set
within form data, e.g., when ``request.params['somename']`` is accessed.
If you are using the :class:`~pyramid.response.Response` class to generate a
-response, or if you use the ``render_template_*`` templating APIs, the UTF-8
-``charset`` is set automatically as the default via the ``Content-Type``
-header. If you return a ``Content-Type`` header without an explicit
-``charset``, a request will add a ``;charset=utf-8`` trailer to the
+response, or if you use the ``pyramid.renderers.render_*`` templating APIs,
+the UTF-8 ``charset`` is set automatically as the default via the
+``Content-Type`` header. If you return a ``Content-Type`` header without an
+explicit ``charset``, a request will add a ``;charset=utf-8`` trailer to the
``Content-Type`` header value for you for response content types that are
textual (e.g., ``text/html`` or ``application/xml``) as it is rendered. If you
are using your own response object, you will need to ensure you do this
yourself.
-.. note:: Only the *values* of request params obtained via ``request.params``,
- ``request.GET`` or ``request.POST`` are decoded to Unicode objects
- implicitly in the :app:`Pyramid` default configuration. The keys are still
- (byte) strings.
-
-
.. index::
single: view calling convention
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index 89dc6e0f1..665bbddc9 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -188,15 +188,10 @@ of them. Here are a couple that might be useful:
Text (Unicode)
++++++++++++++
-Many of the properties of the request object will be text values (``unicode``
-under Python 2 or ``str`` under Python 3) if the request encoding/charset is
-provided. If it is provided, the values in ``req.POST``, ``req.GET``,
-``req.params``, and ``req.cookies`` will contain text. The client *can*
-indicate the charset with something like ``Content-Type:
-application/x-www-form-urlencoded; charset=utf8``, but browsers seldom set
-this. You can reset the charset of an existing request with ``newreq =
-req.decode('utf-8')``, or during instantiation with ``Request(environ,
-charset='utf8')``.
+Most of the properties of the request object will be text values.
+The values in ``req.POST``, ``req.GET``, ``req.params``, and ``req.cookies`` will contain text and are generated assuming a UTF-8 charset.
+The client *can* indicate the charset with something like ``Content-Type: application/x-www-form-urlencoded; charset=utf8``, but browsers seldom set this.
+You can reset the charset of an existing request with ``newreq = req.decode('utf-8')``, or during instantiation with ``Request(environ, charset='utf8')``.
.. index::
single: multidict (WebOb)
@@ -264,7 +259,7 @@ to a :app:`Pyramid` application:
jQuery.ajax({type:'POST',
url: 'http://localhost:6543/', // the pyramid server
data: JSON.stringify({'a':1}),
- contentType: 'application/json; charset=utf-8'});
+ contentType: 'application/json'});
When such a request reaches a view in your application, the
``request.json_body`` attribute will be available in the view callable body.
@@ -280,7 +275,7 @@ For the above view, printed to the console will be:
.. code-block:: python
- {u'a': 1}
+ {'a': 1}
For bonus points, here's a bit of client-side code that will produce a request
that has a body suitable for reading via ``request.json_body`` using Python's
@@ -386,8 +381,8 @@ A response object has three fundamental parts:
``response.app_iter``
An iterable (such as a list or generator) that will produce the content of
- the response. This is also accessible as ``response.body`` (a string),
- ``response.text`` (a unicode object, informed by ``response.charset``), and
+ the response. This is also accessible as ``response.body`` (bytes),
+ ``response.text`` (a Unicode string, informed by ``response.charset``), and
``response.body_file`` (a file-like object; writing to it appends to
``app_iter``).
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index 1e2c71cf0..fbafead66 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -15,12 +15,9 @@ If you would prefer to cut and paste the example code in this tour you may brows
Installation
============
-Once you have a standard Python environment setup, getting started with Pyramid
-is a breeze. Unfortunately "standard" is not so simple in Python. For this
-Quick Tour, it means `Python <https://www.python.org/downloads/>`_, :mod:`python:venv` (or `virtualenv for
-Python 2.7 <https://virtualenv.pypa.io/en/stable/>`_),
-`pip <https://pypi.org/project/pip/>`_, and `Setuptools
-<https://pypi.org/project/setuptools/>`_.
+Once you have a standard Python environment setup, getting started with Pyramid is a breeze.
+Unfortunately "standard" is not so simple in Python.
+For this Quick Tour, it means `Python <https://www.python.org/downloads/>`_, :mod:`python:venv`, `pip <https://pypi.org/project/pip/>`_, and `Setuptools <https://pypi.org/project/setuptools/>`_.
To save a little bit of typing and to be certain that we use the modules,
scripts, and packages installed in our virtual environment, we'll set an
@@ -52,10 +49,8 @@ For Windows:
# or for a specific released version
c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
-Of course Pyramid runs fine on Python 2.7+, as do the examples in this *Quick
-Tour*. We're showing Python 3 for simplicity. (Pyramid had production support
-for Python 3 in October 2011.) Also for simplicity, the remaining examples will
-show only Unix commands.
+As of version 2.0, Pyramid runs on Python 3 only.
+For simplicity, the remaining examples will show only Unix commands.
.. seealso:: See also:
:ref:`Quick Tutorial section on Requirements <qtut_requirements>`,
diff --git a/docs/quick_tour/logging/setup.py b/docs/quick_tour/logging/setup.py
index 27b025384..e9c15db04 100644
--- a/docs/quick_tour/logging/setup.py
+++ b/docs/quick_tour/logging/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py
index 27b025384..e9c15db04 100644
--- a/docs/quick_tour/package/setup.py
+++ b/docs/quick_tour/package/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tour/sessions/setup.py b/docs/quick_tour/sessions/setup.py
index 27b025384..e9c15db04 100644
--- a/docs/quick_tour/sessions/setup.py
+++ b/docs/quick_tour/sessions/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py
index 76cd518ca..28a8e0815 100644
--- a/docs/quick_tour/sqla_demo/setup.py
+++ b/docs/quick_tour/sqla_demo/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/quick_tour/views/views.py b/docs/quick_tour/views/views.py
index 95a2b60ca..ffbe1d893 100644
--- a/docs/quick_tour/views/views.py
+++ b/docs/quick_tour/views/views.py
@@ -1,4 +1,4 @@
-from pyramid.compat import escape
+from html import escape
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
@@ -16,7 +16,7 @@ def home_view(request):
def hello_view(request):
name = request.params.get('name', 'No Name')
body = '<p>Hi %s, this <a href="/goto">redirects</a></p>'
- # pyramid.compat.escape to prevent Cross-Site Scripting (XSS) [CWE 79]
+ # Python html.escape to prevent Cross-Site Scripting (XSS) [CWE 79]
return Response(body % escape(name))
diff --git a/docs/quick_tutorial/cookiecutters/setup.py b/docs/quick_tutorial/cookiecutters/setup.py
index 9482e7c32..d5d3d018b 100644
--- a/docs/quick_tutorial/cookiecutters/setup.py
+++ b/docs/quick_tutorial/cookiecutters/setup.py
@@ -17,8 +17,8 @@ requires = [
]
tests_require = [
- 'WebTest >= 1.3.1', # py3 compat
- 'pytest>=3.7.4',
+ 'WebTest',
+ 'pytest',
'pytest-cov',
]
diff --git a/docs/quick_tutorial/index.rst b/docs/quick_tutorial/index.rst
index b5b7b3313..d727502e9 100644
--- a/docs/quick_tutorial/index.rst
+++ b/docs/quick_tutorial/index.rst
@@ -4,8 +4,7 @@
Quick Tutorial for Pyramid
==========================
-Pyramid is a web framework for Python 2 and 3. This tutorial gives a Python
-3/2-compatible, high-level tour of the major features.
+This tutorial gives a high-level tour of the major features.
This hands-on tutorial covers "a little about a lot": practical introductions
to the most common facilities. Fun, fast-paced, and most certainly not aimed at
diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst
index a74c07673..2ed9b8b55 100644
--- a/docs/quick_tutorial/requirements.rst
+++ b/docs/quick_tutorial/requirements.rst
@@ -19,12 +19,11 @@ virtual environment.)
This *Quick Tutorial* is based on:
-* **Python 3.7**. Pyramid fully supports Python 3.4+ and Python 2.7+. This
- tutorial uses **Python 3.7** but runs fine under Python 2.7.
+* **Python 3.7**. Pyramid fully supports Python 3.4+.
+ This tutorial uses **Python 3.7**.
-* **venv**. We believe in virtual environments. For this tutorial, we use
- Python 3's built-in solution :term:`venv`. For Python 2.7, you can install
- :term:`virtualenv`.
+* **venv**. We believe in virtual environments.
+ For this tutorial, we use Python 3's built-in solution :term:`venv`.
* **pip**. We use :term:`pip` for package management.
@@ -158,8 +157,7 @@ environment variable.
# Windows
python -m venv %VENV%
-.. seealso:: See also Python 3's :mod:`venv module <python:venv>` and Python
- 2's `virtualenv <https://virtualenv.pypa.io/en/latest/>`_ package.
+.. seealso:: See also :mod:`venv module <python:venv>`.
Update packaging tools in the virtual environment
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