Paul Everitt
2013-09-13 b1b92284f496800a4dfd2cea72cb9be07ba8661c
First cut at import of quick tutorial.
179 files added
2 files modified
7527 ■■■■■ changed files
docs/conf.py 31 ●●●●● patch | view | raw | blame | history
docs/index.rst 4 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication.rst 134 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication/development.ini 42 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication/tutorial/__init__.py 24 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication/tutorial/home.pt 18 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication/tutorial/login.pt 25 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication/tutorial/security.py 8 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication/tutorial/tests.py 47 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authentication/tutorial/views.py 64 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization.rst 112 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/development.ini 42 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/tutorial/__init__.py 25 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/tutorial/home.pt 18 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/tutorial/login.pt 25 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/tutorial/resources.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/tutorial/security.py 8 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/tutorial/tests.py 47 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/authorization/tutorial/views.py 66 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/conf.py 281 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases.rst 184 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/development.ini 49 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/setup.py 20 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/sqltutorial.sqlite patch | view | raw | blame | history
docs/quick_tutorial/databases/tutorial/__init__.py 20 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/tutorial/initialize_db.py 37 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/tutorial/models.py 35 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/tutorial/tests.py 58 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/tutorial/views.py 96 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/tutorial/wiki_view.pt 19 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt 22 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/databases/tutorial/wikipage_view.pt 17 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/debugtoolbar.rst 87 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/debugtoolbar/development.ini 40 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/debugtoolbar/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/debugtoolbar/tutorial/__init__.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms.rst 146 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms/setup.py 14 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms/tutorial/__init__.py 12 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms/tutorial/tests.py 47 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms/tutorial/views.py 96 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms/tutorial/wiki_view.pt 19 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt 22 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/forms/tutorial/wikipage_view.pt 17 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/functional_testing.rst 68 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/functional_testing/development.ini 40 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/functional_testing/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/functional_testing/tutorial/__init__.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/functional_testing/tutorial/tests.py 31 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/hello_world.rst 111 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/hello_world/app.py 17 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/index.rst 51 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/ini.rst 144 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/ini/development.ini 38 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/ini/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/ini/tutorial/__init__.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2.rst 96 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2/development.ini 42 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2/tutorial/__init__.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2/tutorial/home.jinja2 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2/tutorial/home.pt 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2/tutorial/tests.py 50 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/jinja2/tutorial/views.py 18 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/json.rst 101 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/json/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/json/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/json/tutorial/__init__.py 10 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/json/tutorial/home.pt 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/json/tutorial/tests.py 50 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/json/tutorial/views.py 19 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/logging.rst 77 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/logging/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/logging/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/logging/tutorial/__init__.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/logging/tutorial/home.pt 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/logging/tutorial/tests.py 44 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/logging/tutorial/views.py 23 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes.rst 180 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/tutorial/__init__.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/tutorial/delete.pt 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/tutorial/edit.pt 10 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/tutorial/hello.pt 16 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/tutorial/home.pt 12 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/tutorial/tests.py 31 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/more_view_classes/tutorial/views.py 39 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/package.rst 112 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/package/setup.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/package/tutorial/__init__.py 1 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/package/tutorial/app.py 17 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/pyramid_setup.rst 27 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/python_setup.rst 88 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/request_response.rst 101 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/request_response/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/request_response/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/request_response/tutorial/__init__.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/request_response/tutorial/home.pt 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/request_response/tutorial/tests.py 54 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/request_response/tutorial/views.py 22 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/rest_ajax.rst 62 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/rest_ajax_layout.rst 50 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/rest_bootstrap.rst 88 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/routing.rst 119 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/routing/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/routing/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/routing/tutorial/__init__.py 8 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/routing/tutorial/home.pt 10 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/routing/tutorial/tests.py 36 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/routing/tutorial/views.py 20 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds.rst 84 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/CHANGES.txt 4 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/MANIFEST.in 2 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/README.txt 1 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/development.ini 60 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/production.ini 54 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/__init__.py 11 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css 8 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css 372 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt 76 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/tests.py 17 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/scaffolds/views.py 6 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/setup.cfg 27 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/scaffolds/setup.py 39 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/sessions.rst 98 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/sessions/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/sessions/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/sessions/tutorial/__init__.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/sessions/tutorial/home.pt 10 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/sessions/tutorial/tests.py 44 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/sessions/tutorial/views.py 29 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/static_assets.rst 89 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/static_assets/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/static_assets/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/static_assets/tutorial/__init__.py 10 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/static_assets/tutorial/home.pt 11 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/static_assets/tutorial/static/app.css 4 ●●●● patch | view | raw | blame | history
docs/quick_tutorial/static_assets/tutorial/tests.py 44 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/static_assets/tutorial/views.py 18 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/templating.rst 100 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/templating/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/templating/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/templating/tutorial/__init__.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/templating/tutorial/home.pt 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/templating/tutorial/tests.py 44 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/templating/tutorial/views.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/traversal_addcontent.rst 105 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/traversal_hierarchy.rst 106 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/traversal_siteroot.rst 153 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/traversal_typeviews.rst 112 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/traversal_zodb.rst 121 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/tutorial_approach.rst 45 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/unit_testing.rst 117 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/unit_testing/development.ini 40 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/unit_testing/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/unit_testing/tutorial/__init__.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/unit_testing/tutorial/tests.py 18 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/view_classes.rst 96 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/view_classes/development.ini 41 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/view_classes/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/view_classes/tutorial/__init__.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/view_classes/tutorial/home.pt 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/view_classes/tutorial/tests.py 44 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/view_classes/tutorial/views.py 17 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/views.rst 120 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/views/development.ini 40 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/views/setup.py 13 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/views/tutorial/__init__.py 9 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/views/tutorial/tests.py 44 ●●●●● patch | view | raw | blame | history
docs/quick_tutorial/views/tutorial/views.py 14 ●●●●● patch | view | raw | blame | history
docs/conf.py
@@ -90,8 +90,39 @@
    'zcml':
        ('http://docs.pylonsproject.org/projects/pyramid_zcml/en/latest',
         None),
    'pyramid': (
        'http://docs.pylonsproject.org/projects/pyramid/en/latest/',
        None)
}
#intersphinx_mapping = {
#    'python': (
#        'http://docs.python.org/2',
#        None),
#    'sqla': (
#        'http://docs.sqlalchemy.org/en/latest',
#        None),
#    'pyramid': (
#        'http://docs.pylonsproject.org/projects/pyramid/en/latest/',
#        None),
#    'jinja2': (
#        'http://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/',
#        None),
#    'toolbar': (
#        'http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest',
#        None),
#    'deform': (
#        'http://docs.pylonsproject.org/projects/deform/en/latest',
#        None),
#    'colander': (
#        'http://docs.pylonsproject.org/projects/colander/en/latest',
#        None),
#    'tutorials': (
#        'http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/',
#        None),
#}
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
docs/index.rst
@@ -44,10 +44,14 @@
   :hidden:
   quick_tour
   quick_tutorial/index
* :doc:`quick_tour` goes through the major features in Pyramid, covering
  a little about a lot.
* :doc:`quick_tutorial/index` does the same, but in a tutorial format:
  deeper treatment of each topic and with working code.
* To see a minimal Pyramid web application, check out
  :ref:`firstapp_chapter`.
docs/quick_tutorial/authentication.rst
New file
@@ -0,0 +1,134 @@
==============================
20: Logins With Authentication
==============================
Login views that authenticate a username/password against a list of
users.
Background
==========
Most web applications have URLs that allow people to add/edit/delete
content via a web browser. Time to add
:ref:`security <security_chapter>`
to the application. In this first step we introduce authentication.
That is, logging in and logging out using Pyramid's rich facilities for
pluggable user storages.
In the next step we will introduce protection resources with
authorization security statements.
Objectives
==========
- Introduce the Pyramid concepts of authentication
- Create login/logout views
Steps
=====
#. We are going to use the view classes step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes authentication; cd authentication
    (env27)$ python setup.py develop
#. Put the security hash in the ``authentication/development.ini``
   configuration file as ``tutorial.secret`` instead of putting it in
   the code:
   .. literalinclude:: authentication/development.ini
    :language: ini
    :linenos:
#. Get authentication (and for now, authorization policies) and login
   route into the :term:`configurator` in
   ``authentication/tutorial/__init__.py``:
   .. literalinclude:: authentication/tutorial/__init__.py
    :linenos:
#. Create a ``authentication/tutorial/security.py`` module that can find
   our user information by providing an *authentication policy callback*:
   .. literalinclude:: authentication/tutorial/security.py
    :linenos:
#. Update the views in ``authentication/tutorial/views.py``:
   .. literalinclude:: authentication/tutorial/views.py
    :linenos:
#. Add a login template at ``authentication/tutorial/login.pt``:
   .. literalinclude:: authentication/tutorial/login.pt
    :language: html
    :linenos:
#. Provide a login/logout box in ``authentication/tutorial/home.pt``
   .. literalinclude:: authentication/tutorial/home.pt
    :language: html
    :linenos:
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in a browser.
#. Click the "Log In" link.
#. Submit the login form with the username ``editor`` and the password
   ``editor``.
#. Note that the "Log In" link has changed to "Logout".
#. Click the "Logout" link.
Analysis
========
Unlike many web frameworks, Pyramid includes a built-in (but optional)
security model for authentication and authorization. This security
system is intended to be flexible and support many needs. In this
security model, authentication (who are you) and authorization (what
are you allowed to do) are not just pluggable, but de-coupled. To learn
one step at a time, we provide a system that identifies users and lets
them log out.
In this example we chose to use the bundled
:ref:`AuthTktAuthenticationPolicy <pyramid:authentication_module>`
policy. We enabled it in our configuration and provided a
ticket-signing secret in our INI file.
Our view class grew a login view. When you reached it via a GET,
it returned a login form. When reached via POST, it processed the
username and password against the "groupfinder" callable that we
registered in the configuration.
In our template, we fetched the ``logged_in`` value from the view
class. We use this to calculate the logged-in user,
if any. In the template we can then choose to show a login link to
anonymous visitors or a logout link to logged-in users.
Extra Credit
============
#. What is the difference between a user and a principal?
#. Can I use a database behind my ``groupfinder`` to look up principals?
#. Do I have to put a ``renderer`` in my ``@forbidden_view_config``
   decorator?
#. Once I am logged in, does any user-centric information get jammed
   onto each request? Use ``import pdb; pdb.set_trace()`` to answer
   this.
.. seealso:: :ref:`pyramid:security_chapter`,
   :ref:`AuthTktAuthenticationPolicy <pyramid:authentication_module>`
docs/quick_tutorial/authentication/development.ini
New file
@@ -0,0 +1,42 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
tutorial.secret = 98zd
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/authentication/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/authentication/tutorial/__init__.py
New file
@@ -0,0 +1,24 @@
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.config import Configurator
from .security import groupfinder
def main(global_config, **settings):
    config = Configurator(settings=settings)
    # Security policies
    authn_policy = AuthTktAuthenticationPolicy(
        settings['tutorial.secret'], callback=groupfinder,
        hashalg='sha512')
    authz_policy = ACLAuthorizationPolicy()
    config.set_authentication_policy(authn_policy)
    config.set_authorization_policy(authz_policy)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.add_route('login', '/login')
    config.add_route('logout', '/logout')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/authentication/tutorial/home.pt
New file
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<div>
    <a tal:condition="view.logged_in is None"
            href="${request.application_url}/login">Log In</a>
    <a tal:condition="view.logged_in is not None"
            href="${request.application_url}/logout">Logout</a>
</div>
<h1>Hi ${name}</h1>
<p>Visit <a href="${request.route_url('hello')}">hello</a></p>
</body>
</html>
docs/quick_tutorial/authentication/tutorial/login.pt
New file
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Login</h1>
<span tal:replace="message"/>
<form action="${url}" method="post">
    <input type="hidden" name="came_from"
           value="${came_from}"/>
    <label for="login">Username</label>
    <input type="text" id="login"
           name="login"
           value="${login}"/><br/>
    <label for="password">Password</label>
    <input type="password" id="password"
           name="password"
           value="${password}"/><br/>
    <input type="submit" name="form.submitted"
           value="Log In"/>
</form>
</body>
</html>
docs/quick_tutorial/authentication/tutorial/security.py
New file
@@ -0,0 +1,8 @@
USERS = {'editor': 'editor',
         'viewer': 'viewer'}
GROUPS = {'editor': ['group:editors']}
def groupfinder(userid, request):
    if userid in USERS:
        return GROUPS.get(userid, [])
docs/quick_tutorial/authentication/tutorial/tests.py
New file
@@ -0,0 +1,47 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/authentication/tutorial/views.py
New file
@@ -0,0 +1,64 @@
from pyramid.httpexceptions import HTTPFound
from pyramid.security import (
    remember,
    forget,
    authenticated_userid
    )
from pyramid.view import (
    view_config,
    view_defaults
    )
from .security import USERS
@view_defaults(renderer='home.pt')
class TutorialViews:
    def __init__(self, request):
        self.request = request
        self.logged_in = authenticated_userid(request)
    @view_config(route_name='home')
    def home(self):
        return {'name': 'Home View'}
    @view_config(route_name='hello')
    def hello(self):
        return {'name': 'Hello View'}
    @view_config(route_name='login', renderer='login.pt')
    def login(self):
        request = self.request
        login_url = request.route_url('login')
        referrer = request.url
        if referrer == login_url:
            referrer = '/'  # never use login form itself as came_from
        came_from = request.params.get('came_from', referrer)
        message = ''
        login = ''
        password = ''
        if 'form.submitted' in request.params:
            login = request.params['login']
            password = request.params['password']
            if USERS.get(login) == password:
                headers = remember(request, login)
                return HTTPFound(location=came_from,
                                 headers=headers)
            message = 'Failed login'
        return dict(
            name='Login',
            message=message,
            url=request.application_url + '/login',
            came_from=came_from,
            login=login,
            password=password,
        )
    @view_config(route_name='logout')
    def logout(self):
        request = self.request
        headers = forget(request)
        url = request.route_url('home')
        return HTTPFound(location=url,
                         headers=headers)
docs/quick_tutorial/authorization.rst
New file
@@ -0,0 +1,112 @@
===========================================
21: Protecting Resources With Authorization
===========================================
Assign security statements to resources describing the permissions
required to perform an operation.
Background
==========
Our application has URLs that allow people to add/edit/delete content
via a web browser. Time to add security to the application. Let's
protect our add/edit views to require a login (username of
``editor`` and password of ``editor``.) We will allow the other views
to continue working without a password.
Objectives
==========
- Introduce the Pyramid concepts of authentication, authorization,
  permissions, and access control lists (ACLs)
- Make a :term:`root factory` that returns an instance of our
  class for the top of the application
- Assign security statements to our root resource
- Add a permissions predicate on a view
- Provide a :term:`Forbidden view` to handle visiting a URL without
  adequate permissions
Steps
=====
#. We are going to use the authentication step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r authentication authorization; cd authorization
    (env27)$ python setup.py develop
#. Start by changing ``authorization/tutorial/__init__.py`` to
   specify a root factory to the :term:`pyramid:configurator`:
   .. literalinclude:: authorization/tutorial/__init__.py
    :linenos:
#. That means we need to implement
   ``authorization/tutorial/resources.py``
   .. literalinclude:: authorization/tutorial/resources.py
    :linenos:
#. Change ``authorization/tutorial/views.py`` to require the ``edit``
   permission on the ``hello`` view and implement the forbidden view:
   .. literalinclude:: authorization/tutorial/views.py
    :linenos:
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in a browser.
#. If you are still logged in, click the "Log Out" link.
#. Visit ``http://localhost:6543/howdy`` in a browser. You should be
   asked to login.
Analysis
========
This simple tutorial step can be boiled down to the following:
- A view can require a *permission* (``edit``)
- The context for our view (the ``Root``) has an access control list
  (ACL)
- This ACL says that the ``edit`` permission is available on ``Root``
  to the ``group:editors`` *principal*
- The registered ``groupfinder`` answers whether a particular user
  (``editor``) has a particular group (``group:editors``)
In summary: ``hello`` wants ``edit`` permission, ``Root`` says
``group:editors`` has ``edit`` permission.
Of course, this only applies on ``Root``. Some other part of the site
(a.k.a. *context*) might have a different ACL.
If you are not logged in and visit ``/hello``, you need to get
shown the login screen. How does Pyramid know what is the login page to
use? We explicitly told Pyramid that the ``login`` view should be used
by decorating the view with ``@forbidden_view_config``.
Extra Credit
============
#. Perhaps you would like experience of not having enough permissions
   (forbidden) to be richer. How could you change this?
#. Perhaps we want to store security statements in a database and
   allow editing via a browser. How might this be done?
#. What if we want different security statements on different kinds of
   objects? Or on the same kinds of objects, but in different parts of a
   URL hierarchy?
docs/quick_tutorial/authorization/development.ini
New file
@@ -0,0 +1,42 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
tutorial.secret = 98zd
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/authorization/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/authorization/tutorial/__init__.py
New file
@@ -0,0 +1,25 @@
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.config import Configurator
from .security import groupfinder
def main(global_config, **settings):
    config = Configurator(settings=settings,
                          root_factory='.resources.Root')
    # Security policies
    authn_policy = AuthTktAuthenticationPolicy(
        settings['tutorial.secret'], callback=groupfinder,
        hashalg='sha512')
    authz_policy = ACLAuthorizationPolicy()
    config.set_authentication_policy(authn_policy)
    config.set_authorization_policy(authz_policy)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.add_route('login', '/login')
    config.add_route('logout', '/logout')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/authorization/tutorial/home.pt
New file
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<div>
    <a tal:condition="view.logged_in is None"
            href="${request.application_url}/login">Log In</a>
    <a tal:condition="view.logged_in is not None"
            href="${request.application_url}/logout">Logout</a>
</div>
<h1>Hi ${name}</h1>
<p>Visit <a href="${request.route_url('hello')}">hello</a></p>
</body>
</html>
docs/quick_tutorial/authorization/tutorial/login.pt
New file
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Login</h1>
<span tal:replace="message"/>
<form action="${url}" method="post">
    <input type="hidden" name="came_from"
           value="${came_from}"/>
    <label for="login">Username</label>
    <input type="text" id="login"
           name="login"
           value="${login}"/><br/>
    <label for="password">Password</label>
    <input type="password" id="password"
           name="password"
           value="${password}"/><br/>
    <input type="submit" name="form.submitted"
           value="Log In"/>
</form>
</body>
</html>
docs/quick_tutorial/authorization/tutorial/resources.py
New file
@@ -0,0 +1,9 @@
from pyramid.security import Allow, Everyone
class Root(object):
    __acl__ = [(Allow, Everyone, 'view'),
               (Allow, 'group:editors', 'edit')]
    def __init__(self, request):
        pass
docs/quick_tutorial/authorization/tutorial/security.py
New file
@@ -0,0 +1,8 @@
USERS = {'editor': 'editor',
         'viewer': 'viewer'}
GROUPS = {'editor': ['group:editors']}
def groupfinder(userid, request):
    if userid in USERS:
        return GROUPS.get(userid, [])
docs/quick_tutorial/authorization/tutorial/tests.py
New file
@@ -0,0 +1,47 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/authorization/tutorial/views.py
New file
@@ -0,0 +1,66 @@
from pyramid.httpexceptions import HTTPFound
from pyramid.security import (
    remember,
    forget,
    authenticated_userid
    )
from pyramid.view import (
    view_config,
    view_defaults,
    forbidden_view_config
    )
from .security import USERS
@view_defaults(renderer='home.pt')
class TutorialViews:
    def __init__(self, request):
        self.request = request
        self.logged_in = authenticated_userid(request)
    @view_config(route_name='home')
    def home(self):
        return {'name': 'Home View'}
    @view_config(route_name='hello', permission='edit')
    def hello(self):
        return {'name': 'Hello View'}
    @view_config(route_name='login', renderer='login.pt')
    @forbidden_view_config(renderer='login.pt')
    def login(self):
        request = self.request
        login_url = request.route_url('login')
        referrer = request.url
        if referrer == login_url:
            referrer = '/'  # never use login form itself as came_from
        came_from = request.params.get('came_from', referrer)
        message = ''
        login = ''
        password = ''
        if 'form.submitted' in request.params:
            login = request.params['login']
            password = request.params['password']
            if USERS.get(login) == password:
                headers = remember(request, login)
                return HTTPFound(location=came_from,
                                 headers=headers)
            message = 'Failed login'
        return dict(
            name='Login',
            message=message,
            url=request.application_url + '/login',
            came_from=came_from,
            login=login,
            password=password,
        )
    @view_config(route_name='logout')
    def logout(self):
        request = self.request
        headers = forget(request)
        url = request.route_url('home')
        return HTTPFound(location=url,
                         headers=headers)
docs/quick_tutorial/conf.py
New file
@@ -0,0 +1,281 @@
# -*- coding: utf-8 -*-
#
# Getting Started with Pyramid and REST documentation build configuration file, created by
# sphinx-quickstart on Mon Aug 26 14:44:57 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.intersphinx']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Getting Started with Pyramid and REST'
copyright = u'2013, Agendaless Consulting'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '1.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'GettingStartedwithPyramidandRESTdoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    #'papersize': 'letterpaper',
    # The font size ('10pt', '11pt' or '12pt').
    #'pointsize': '10pt',
    # Additional stuff for the LaTeX preamble.
    #'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
    ('index', 'GettingStartedwithPyramidandREST.tex',
     u'Getting Started with Pyramid and REST Documentation',
     u'Agendaless Consulting', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    ('index', 'gettingstartedwithpyramidandrest',
     u'Getting Started with Pyramid and REST Documentation',
     [u'Agendaless Consulting'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    ('index', 'GettingStartedwithPyramidandREST',
     u'Getting Started with Pyramid and REST Documentation',
     u'Agendaless Consulting', 'GettingStartedwithPyramidandREST',
     'One line description of project.',
     'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
    'python': (
        'http://docs.python.org/2',
        None),
    'sqla': (
        'http://docs.sqlalchemy.org/en/latest',
        None),
    'pyramid': (
        'http://docs.pylonsproject.org/projects/pyramid/en/latest/',
        None),
    'jinja2': (
        'http://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/',
        None),
    'toolbar': (
        'http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest',
        None),
    'deform': (
        'http://docs.pylonsproject.org/projects/deform/en/latest',
        None),
    'colander': (
        'http://docs.pylonsproject.org/projects/colander/en/latest',
        None),
    'tutorials': (
        'http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/',
        None),
}
docs/quick_tutorial/databases.rst
New file
@@ -0,0 +1,184 @@
==============================
19: Databases Using SQLAlchemy
==============================
Store/retrieve data using the SQLAlchemy ORM atop the SQLite database.
Background
==========
Our Pyramid-based wiki application now needs database-backed storage of
pages. This frequently means a SQL database. The Pyramid community
strongly supports the
:ref:`SQLAlchemy <sqla:index_toplevel>` project and its
:ref:`object-relational mapper (ORM) <sqla:ormtutorial_toplevel>`
as a convenient, Pythonic way to interface to databases.
In this step we hook up SQLAlchemy to a SQLite database table,
providing storage and retrieval for the wikipages in the previous step.
.. note::
    The ``alchemy`` scaffold is really helpful for getting a
    SQLAlchemy project going, including generation of the console
    script. Since we want to see all the decisions, we will forgo
    convenience in this tutorial and wire it up ourselves.
Objectives
==========
- Store pages in SQLite by using SQLAlchemy models
- Use SQLAlchemy queries to list/add/view/edit pages
- Provide a database-initialize command by writing a Pyramid *console
  script* which can be run from the command line
Steps
=====
#. We are going to use the forms step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r forms databases; cd databases
#. We need to add some dependencies in ``databases/setup.py`` as well
   as an "entry point" for the command-line script:
   .. literalinclude:: databases/setup.py
    :linenos:
   .. note::
     We aren't yet doing ``python3.3 setup.py develop`` as we
     are changing it later.
#. Our configuration file at ``databases/development.ini`` wires
   together some new pieces:
   .. literalinclude:: databases/development.ini
    :language: ini
#. This engine configuration now needs to be read into the application
   through changes in ``databases/tutorial/__init__.py``:
   .. literalinclude:: databases/tutorial/__init__.py
    :linenos:
#. Make a command-line script at ``databases/tutorial/initialize_db.py``
   to initialize the database:
   .. literalinclude:: databases/tutorial/initialize_db.py
#. Since ``setup.py`` changed, we now run it:
   .. code-block:: bash
    (env27)$ python setup.py develop
#. The script references some models in ``databases/tutorial/models.py``:
   .. literalinclude:: databases/tutorial/models.py
    :linenos:
#. Let's run this console script, thus producing our database and table:
   .. code-block:: bash
    (env27)$ initialize_tutorial_db development.ini
    2013-09-06 15:54:08,050 INFO  [sqlalchemy.engine.base.Engine][MainThread] PRAGMA table_info("wikipages")
    2013-09-06 15:54:08,050 INFO  [sqlalchemy.engine.base.Engine][MainThread] ()
    2013-09-06 15:54:08,051 INFO  [sqlalchemy.engine.base.Engine][MainThread]
    CREATE TABLE wikipages (
            uid INTEGER NOT NULL,
            title TEXT,
            body TEXT,
            PRIMARY KEY (uid),
            UNIQUE (title)
    )
#. With our data now driven by SQLAlchemy queries, we need to update
   our ``databases/tutorial/views.py``:
   .. literalinclude:: databases/tutorial/views.py
#. Our tests in ``databases/tutorial/tests.py`` changed to include
   SQLAlchemy bootstrapping:
   .. literalinclude:: databases/tutorial/tests.py
    :linenos:
#. Run the tests in your package using ``nose``:
    .. code-block:: bash
        (env27)$ nosetests .
        ..
        -----------------------------------------------------------------
        Ran 2 tests in 1.141s
        OK
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in a browser.
Analysis
========
Let's start with the dependencies. We made the decision to use
``SQLAlchemy`` to talk to our database. We also, though, installed
``pyramid_tm`` and ``zope.sqlalchemy``. Why?
Pyramid has a strong orientation towards support for ``transactions``.
Specifically, you can install a transaction manager into your app
application, either as middleware or a Pyramid "tween". Then,
just before you return the response, all transaction-aware parts of
your application are executed.
This means Pyramid view code usually doesn't manage transactions. If
your view code or a template generates an error, the transaction manager
aborts the transaction. This is a very liberating way to write code.
The ``pyramid_tm`` package provides a "tween" that is configured in the
``development.ini`` configuration file. That installs it. We then need
a package that makes SQLAlchemy and thus the RDBMS transaction manager
integrate with the Pyramid transaction manager. That's what
``zope.sqlalchemy`` does.
Where do we point at the location on disk for the SQLite file? In the
configuration file. This lets consumers of our package change the
location in a safe (non-code) way. That is, in configuration. This
configuration-oriented approach isn't required in Pyramid; you can
still make such statements in your ``__init__.py`` or some companion
module.
The ``initialize_tutorial_db`` is a nice example of framework support.
You point your setup at the location of some ``[console_scripts]`` and
these get generated into your virtualenv's ``bin`` directory. Our
console script follows the pattern of being fed a configuration file
with all the bootstrapping. It then opens SQLAlchemy and creates the
root of the wiki, which also makes the SQLite file. Note the
``with transaction.manager`` part that puts the work in the scope of a
transaction (as we aren't inside a web request where this is done
automatically.)
The ``models.py`` does a little bit extra work to hook up SQLAlchemy
into the Pyramid transaction manager. It then declares the model for a
``Page``.
Our views have changes primarily around replacing our dummy
dictionary-of-dictionaries data with proper database support: list the
rows, add a row, edit a row, and delete a row.
Extra Credit
============
#. Why all this code? Why can't I just type 2 lines and have magic ensue?
#. Give a try at a button that deletes a wiki page.
docs/quick_tutorial/databases/development.ini
New file
@@ -0,0 +1,49 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
    pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial, sqlalchemy
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[logger_sqlalchemy]
level = INFO
handlers =
qualname = sqlalchemy.engine
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/databases/setup.py
New file
@@ -0,0 +1,20 @@
from setuptools import setup
requires = [
    'pyramid',
    'deform',
    'sqlalchemy',
    'pyramid_tm',
    'zope.sqlalchemy',
    'pysqlite'
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      [console_scripts]
      initialize_tutorial_db = tutorial.initialize_db:main
      """,
)
docs/quick_tutorial/databases/sqltutorial.sqlite
Binary files differ
docs/quick_tutorial/databases/tutorial/__init__.py
New file
@@ -0,0 +1,20 @@
from pyramid.config import Configurator
from sqlalchemy import engine_from_config
from .models import DBSession, Base
def main(global_config, **settings):
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    config = Configurator(settings=settings,
                          root_factory='tutorial.models.Root')
    config.add_route('wiki_view', '/')
    config.add_route('wikipage_add', '/add')
    config.add_route('wikipage_view', '/{uid}')
    config.add_route('wikipage_edit', '/{uid}/edit')
    config.add_static_view('deform_static', 'deform:static/')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/databases/tutorial/initialize_db.py
New file
@@ -0,0 +1,37 @@
import os
import sys
import transaction
from sqlalchemy import engine_from_config
from pyramid.paster import (
    get_appsettings,
    setup_logging,
    )
from .models import (
    DBSession,
    Page,
    Base,
    )
def usage(argv):
    cmd = os.path.basename(argv[0])
    print('usage: %s <config_uri>\n'
          '(example: "%s development.ini")' % (cmd, cmd))
    sys.exit(1)
def main(argv=sys.argv):
    if len(argv) != 2:
        usage(argv)
    config_uri = argv[1]
    setup_logging(config_uri)
    settings = get_appsettings(config_uri)
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.create_all(engine)
    with transaction.manager:
        model = Page(title='Root', body='<p>Root</p>')
        DBSession.add(model)
docs/quick_tutorial/databases/tutorial/models.py
New file
@@ -0,0 +1,35 @@
from pyramid.security import Allow, Everyone
from sqlalchemy import (
    Column,
    Integer,
    Text,
    )
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
    scoped_session,
    sessionmaker,
    )
from zope.sqlalchemy import ZopeTransactionExtension
DBSession = scoped_session(
    sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()
class Page(Base):
    __tablename__ = 'wikipages'
    uid = Column(Integer, primary_key=True)
    title = Column(Text, unique=True)
    body = Column(Text)
class Root(object):
    __acl__ = [(Allow, Everyone, 'view'),
               (Allow, 'group:editors', 'edit')]
    def __init__(self, request):
        pass
docs/quick_tutorial/databases/tutorial/tests.py
New file
@@ -0,0 +1,58 @@
import unittest
import transaction
from pyramid import testing
def _initTestingDB():
    from sqlalchemy import create_engine
    from .models import (
        DBSession,
        Page,
        Base
        )
    engine = create_engine('sqlite://')
    Base.metadata.create_all(engine)
    DBSession.configure(bind=engine)
    with transaction.manager:
        model = Page(title='FrontPage', body='This is the front page')
        DBSession.add(model)
    return DBSession
class WikiViewTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
        self.config = testing.setUp()
    def tearDown(self):
        self.session.remove()
        testing.tearDown()
    def test_wiki_view(self):
        from tutorial.views import WikiViews
        request = testing.DummyRequest()
        inst = WikiViews(request)
        response = inst.wiki_view()
        self.assertEqual(response['title'], 'Wiki View')
class WikiFunctionalTests(unittest.TestCase):
    def setUp(self):
        self.session = _initTestingDB()
        self.config = testing.setUp()
        from pyramid.paster import get_app
        app = get_app('development.ini')
        from webtest import TestApp
        self.testapp = TestApp(app)
    def tearDown(self):
        self.session.remove()
        testing.tearDown()
    def test_it(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'Wiki: View', res.body)
        res = self.testapp.get('/add', status=200)
        self.assertIn(b'Add/Edit', res.body)
docs/quick_tutorial/databases/tutorial/views.py
New file
@@ -0,0 +1,96 @@
import colander
import deform.widget
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from .models import DBSession, Page
class WikiPage(colander.MappingSchema):
    title = colander.SchemaNode(colander.String())
    body = colander.SchemaNode(
        colander.String(),
        widget=deform.widget.RichTextWidget()
    )
class WikiViews(object):
    def __init__(self, request):
        self.request = request
    @property
    def wiki_form(self):
        schema = WikiPage()
        return deform.Form(schema, buttons=('submit',))
    @property
    def reqts(self):
        return self.wiki_form.get_widget_resources()
    @view_config(route_name='wiki_view', renderer='wiki_view.pt')
    def wiki_view(self):
        pages = DBSession.query(Page).order_by(Page.title)
        return dict(title='Wiki View', pages=pages)
    @view_config(route_name='wikipage_add',
                 renderer='wikipage_addedit.pt')
    def wikipage_add(self):
        form = self.wiki_form.render()
        if 'submit' in self.request.params:
            controls = self.request.POST.items()
            try:
                appstruct = self.wiki_form.validate(controls)
            except deform.ValidationFailure as e:
                # Form is NOT valid
                return dict(form=e.render())
            # Add a new page to the database
            new_title = appstruct['title']
            new_body = appstruct['body']
            DBSession.add(Page(title=new_title, body=new_body))
            # Get the new ID and redirect
            page = DBSession.query(Page).filter_by(title=new_title).one()
            new_uid = page.uid
            url = self.request.route_url('wikipage_view', uid=new_uid)
            return HTTPFound(url)
        return dict(form=form)
    @view_config(route_name='wikipage_view', renderer='wikipage_view.pt')
    def wikipage_view(self):
        uid = int(self.request.matchdict['uid'])
        page = DBSession.query(Page).filter_by(uid=uid).one()
        return dict(page=page)
    @view_config(route_name='wikipage_edit',
                 renderer='wikipage_addedit.pt')
    def wikipage_edit(self):
        uid = int(self.request.matchdict['uid'])
        page = DBSession.query(Page).filter_by(uid=uid).one()
        wiki_form = self.wiki_form
        if 'submit' in self.request.params:
            controls = self.request.POST.items()
            try:
                appstruct = wiki_form.validate(controls)
            except deform.ValidationFailure as e:
                return dict(page=page, form=e.render())
            # Change the content and redirect to the view
            page.title = appstruct['title']
            page.body = appstruct['body']
            url = self.request.route_url('wikipage_view', uid=uid)
            return HTTPFound(url)
        form = self.wiki_form.render(dict(
            uid=page.uid, title=page.title, body=page.body)
        )
        return dict(page=page, form=form)
docs/quick_tutorial/databases/tutorial/wiki_view.pt
New file
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Wiki: View</title>
</head>
<body>
<h1>Wiki</h1>
<a href="${request.route_url('wikipage_add')}">Add
    WikiPage</a>
<ul>
    <li tal:repeat="page pages">
        <a href="${request.route_url('wikipage_view', uid=page.uid)}">
                ${page.title}
        </a>
    </li>
</ul>
</body>
</html>
docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
New file
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>WikiPage: Add/Edit</title>
    <tal:block tal:repeat="reqt view.reqts['css']">
        <link rel="stylesheet" type="text/css"
              href="${request.static_url('deform:static/' + reqt)}"/>
    </tal:block>
    <tal:block tal:repeat="reqt view.reqts['js']">
        <script src="${request.static_url('deform:static/' + reqt)}"
                type="text/javascript"></script>
    </tal:block>
</head>
<body>
<h1>Wiki</h1>
<p>${structure: form}</p>
<script type="text/javascript">
    deform.load()
</script>
</body>
</html>
docs/quick_tutorial/databases/tutorial/wikipage_view.pt
New file
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>WikiPage: View</title>
</head>
<body>
<a href="${request.route_url('wiki_view')}">
    Up
</a> |
<a href="${request.route_url('wikipage_edit', uid=page.uid)}">
    Edit
</a>
<h1>${page.title}</h1>
<p>${structure: page.body}</p>
</body>
</html>
docs/quick_tutorial/debugtoolbar.rst
New file
@@ -0,0 +1,87 @@
============================================
04: Easier Development with ``debugtoolbar``
============================================
Error-handling and introspection using the ``pyramid_debugtoolbar``
add-on.
Background
==========
As we introduce the basics we also want to show how to be productive in
development and debugging. For example, we just discussed template
reloading and earlier we showed ``--reload`` for application reloading.
``pyramid_debugtoolbar`` is a popular Pyramid add-on which makes
several tools available in your browser. Adding it to your project
illustrates several points about configuration.
Objectives
==========
- Install and enable the toolbar to help during development
- Explain Pyramid add-ons
- Show how an add-on gets configured into your application
Steps
=====
#. First we copy the results of the previous step, as well as install
   the ``pyramid_debugtoolbar`` package:
   .. code-block:: bash
    (env27)$ cd ..; cp -r ini debugtoolbar; cd debugtoolbar
    (env27)$ python setup.py develop
    (env27)$ easy_install pyramid_debugtoolbar
#. Our ``debugtoolbar/development.ini`` gets a configuration entry for
   ``pyramid.includes``:
   .. literalinclude:: debugtoolbar/development.ini
    :language: ini
    :linenos:
#. Run the WSGI application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser. See the handy
   toolbar on the right.
Analysis
========
``pyramid_debugtoolbar`` is a full-fledged Python package,
available on PyPI just like thousands of other Python packages. Thus we
start by installing the ``pyramid_debugtoolbar`` package into our
virtual environment using normal Python package installation commands.
The ``pyramid_debugtoolbar`` Python package is also a Pyramid add-on,
which means we need to include its add-on configuration into our web
application. We could do this with imperative configuration in
``tutorial/__init__.py`` by using ``config.include``. Pyramid also
supports wiring in add-on configuration via our ``development.ini``
using ``pyramid.includes``. We use this to load the configuration for
the debugtoolbar.
You'll now see an attractive (and collapsible) menu in the right of
your browser, providing introspective access to debugging information.
Even better, if your web application generates an error,
you will see a nice traceback on the screen. When you want to disable
this toolbar, no need to change code: you can remove it from
``pyramid.includes`` in the relevant ``.ini`` configuration file (thus
showing why configuration files are handy.)
Note that the toolbar mutates the HTML generated by our app and uses jQuery to
overlay itself.  If you are using the toolbar while you're developing and you
start to experience otherwise inexplicable client-side weirdness, you can shut
it off by commenting out the ``pyramid_debugtoolbar`` line in
``pyramid.includes`` temporarily.
.. seealso:: See Also: :ref:`pyramid_debugtoolbar <toolbar:overview>`
docs/quick_tutorial/debugtoolbar/development.ini
New file
@@ -0,0 +1,40 @@
[app:main]
use = egg:tutorial
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/debugtoolbar/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/debugtoolbar/tutorial/__init__.py
New file
@@ -0,0 +1,13 @@
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
    return Response('<body><h1>Hello World!</h1></body>')
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('hello', '/')
    config.add_view(hello_world, route_name='hello')
    return config.make_wsgi_app()
docs/quick_tutorial/forms.rst
New file
@@ -0,0 +1,146 @@
====================================
18: Forms and Validation With Deform
====================================
Schema-driven, autogenerated forms with validation.
Background
==========
Modern web applications deal extensively with forms. Developers,
though, have a wide range of philosophies about how frameworks should
help them with their forms. As such, Pyramid doesn't directly bundle
one particular form library. Instead, there are a variety of form
libraries that are easy to use in Pyramid.
:ref:`Deform <deform:overview>`
is one such library. In this step, we introduce Deform for our
forms and validation. This also gives us the
:ref:`Colander <colander:overview>` for schemas and validation.
Deform is getting a facelift, with styling from Twitter Bootstrap and
advanced widgets from popular JavaScript projects. The work began in
``deform_bootstrap`` and is being merged into an update to Deform.
Objectives
==========
- Make a schema using Colander, the companion to Deform
- Create a form with Deform and change our views to handle validation
Steps
=====
#. First we copy the results of the ``view_classes`` step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes forms; cd forms
#. Let's edit ``forms/setup.py`` to declare a dependency on Deform
   (which then pulls in Colander as a dependency:
   .. literalinclude:: forms/setup.py
    :linenos:
#. We can now install our project in development mode:
   .. code-block:: bash
      (env27)$ python setup.py develop
#. Register a static view in ``forms/tutorial/__init__.py`` for
   Deform's CSS/JS etc. as well as our demo wikipage scenario's
   views:
   .. literalinclude:: forms/tutorial/__init__.py
    :linenos:
#. Implement the new views, as well as the form schemas and some
   dummy data, in ``forms/tutorial/views.py``:
   .. literalinclude:: forms/tutorial/views.py
    :linenos:
#. A template for the top of the "wiki" in
   ``forms/tutorial/wiki_view.pt``:
   .. literalinclude:: forms/tutorial/wiki_view.pt
    :language: html
    :linenos:
#. Another template for adding/editing in
   ``forms/tutorial/wikipage_addedit.pt``:
   .. literalinclude:: forms/tutorial/wikipage_addedit.pt
    :language: html
    :linenos:
#. Finally, a template at ``forms/tutorial/wikipage_view.pt``
   for viewing a wiki page:
   .. literalinclude:: forms/tutorial/wikipage_view.pt
    :language: html
    :linenos:
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in a browser.
Analysis
========
This step helps illustrate the utility of asset specifications for
static assets. We have an outside package called Deform with static
assets which need to be published. We don't have to know where on disk
it is located. We point at the package, then the path inside the package.
We just need to include a call to ``add_static_view`` to make that
directory available at a URL. For Pyramid-specific pages,
Pyramid provides a facility (``config.include()``) which even makes
that unnecessary for consumers of a package. (Deform is not specific to
Pyramid.)
Our forms have rich widgets which need the static CSS and JS just
mentioned. Deform has a :term:`resource registry` which allows widgets
to specify which JS and CSS are needed. Our ``wikipage_addedit.pt``
template shows how we iterated over that data to generate markup that
includes the needed resources.
Our add and edit views use a pattern called *self-posting forms*.
Meaning, the same URL is used to ``GET`` the form as is used to
``POST`` the form. The route, the view, and the template are the same
whether you are walking up to it the first time or you clicked a button.
Inside the view we do ``if 'submit' in self.request.params:`` to see if
this form was a ``POST`` where the user clicked on a particular button
``<input name="submit">``.
The form controller then follows a typical pattern:
- If you are doing a GET, skip over and just return the form
- If you are doing a POST, validate the form contents
- If the form is invalid, bail out by re-rendering the form with the
  supplied ``POST`` data
- If the validation succeeded, perform some action and issue a
  redirect via ``HTTPFound``.
We are, in essence, writing our own form controller. Other
Pyramid-based systems, including ``pyramid_deform``, provide a
form-centric view class which automates much of this branching and
routing.
Extra Credit
============
#. Give a try at a button that goes to a delete view for a
   particular wiki page.
docs/quick_tutorial/forms/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/forms/setup.py
New file
@@ -0,0 +1,14 @@
from setuptools import setup
requires = [
    'pyramid',
    'deform'
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/forms/tutorial/__init__.py
New file
@@ -0,0 +1,12 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('wiki_view', '/')
    config.add_route('wikipage_add', '/add')
    config.add_route('wikipage_view', '/{uid}')
    config.add_route('wikipage_edit', '/{uid}/edit')
    config.add_static_view('deform_static', 'deform:static/')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/forms/tutorial/tests.py
New file
@@ -0,0 +1,47 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/forms/tutorial/views.py
New file
@@ -0,0 +1,96 @@
import colander
import deform.widget
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
pages = {
    '100': dict(uid='100', title='Page 100', body='<em>100</em>'),
    '101': dict(uid='101', title='Page 101', body='<em>101</em>'),
    '102': dict(uid='102', title='Page 102', body='<em>102</em>')
}
class WikiPage(colander.MappingSchema):
    title = colander.SchemaNode(colander.String())
    body = colander.SchemaNode(
        colander.String(),
        widget=deform.widget.RichTextWidget()
    )
class WikiViews(object):
    def __init__(self, request):
        self.request = request
    @property
    def wiki_form(self):
        schema = WikiPage()
        return deform.Form(schema, buttons=('submit',))
    @property
    def reqts(self):
        return self.wiki_form.get_widget_resources()
    @view_config(route_name='wiki_view', renderer='wiki_view.pt')
    def wiki_view(self):
        return dict(pages=pages.values())
    @view_config(route_name='wikipage_add',
                 renderer='wikipage_addedit.pt')
    def wikipage_add(self):
        form = self.wiki_form.render()
        if 'submit' in self.request.params:
            controls = self.request.POST.items()
            try:
                appstruct = self.wiki_form.validate(controls)
            except deform.ValidationFailure as e:
                # Form is NOT valid
                return dict(form=e.render())
            # Form is valid, make a new identifier and add to list
            last_uid = int(sorted(pages.keys())[-1])
            new_uid = str(last_uid + 1)
            pages[new_uid] = dict(
                uid=new_uid, title=appstruct['title'],
                body=appstruct['body']
            )
            # Now visit new page
            url = self.request.route_url('wikipage_view', uid=new_uid)
            return HTTPFound(url)
        return dict(form=form)
    @view_config(route_name='wikipage_view', renderer='wikipage_view.pt')
    def wikipage_view(self):
        uid = self.request.matchdict['uid']
        page = pages[uid]
        return dict(page=page)
    @view_config(route_name='wikipage_edit',
                 renderer='wikipage_addedit.pt')
    def wikipage_edit(self):
        uid = self.request.matchdict['uid']
        page = pages[uid]
        wiki_form = self.wiki_form
        if 'submit' in self.request.params:
            controls = self.request.POST.items()
            try:
                appstruct = wiki_form.validate(controls)
            except deform.ValidationFailure as e:
                return dict(page=page, form=e.render())
            # Change the content and redirect to the view
            page['title'] = appstruct['title']
            page['body'] = appstruct['body']
            url = self.request.route_url('wikipage_view',
                                         uid=page['uid'])
            return HTTPFound(url)
        form = wiki_form.render(page)
        return dict(page=page, form=form)
docs/quick_tutorial/forms/tutorial/wiki_view.pt
New file
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Wiki: View</title>
</head>
<body>
<h1>Wiki</h1>
<a href="${request.route_url('wikipage_add')}">Add
    WikiPage</a>
<ul>
    <li tal:repeat="page pages">
        <a href="${request.route_url('wikipage_view', uid=page.uid)}">
                ${page.title}
        </a>
    </li>
</ul>
</body>
</html>
docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
New file
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>WikiPage: Add/Edit</title>
    <tal:block tal:repeat="reqt view.reqts['css']">
        <link rel="stylesheet" type="text/css"
              href="${request.static_url('deform:static/' + reqt)}"/>
    </tal:block>
    <tal:block tal:repeat="reqt view.reqts['js']">
        <script src="${request.static_url('deform:static/' + reqt)}"
                type="text/javascript"></script>
    </tal:block>
</head>
<body>
<h1>Wiki</h1>
<p>${structure: form}</p>
<script type="text/javascript">
    deform.load()
</script>
</body>
</html>
docs/quick_tutorial/forms/tutorial/wikipage_view.pt
New file
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>WikiPage: View</title>
</head>
<body>
<a href="${request.route_url('wiki_view')}">
    Up
</a> |
<a href="${request.route_url('wikipage_edit', uid=page.uid)}">
    Edit
</a>
<h1>${page.title}</h1>
<p>${structure: page.body}</p>
</body>
</html>
docs/quick_tutorial/functional_testing.rst
New file
@@ -0,0 +1,68 @@
===================================
06: Functional Testing with WebTest
===================================
Write end-to-end full-stack testing using ``webtest``.
Background
==========
Unit tests are a common and popular approach to test-driven development
(TDD.) In web applications, though, the templating and entire apparatus
of a web site are important parts of the delivered quality. We'd like a
way to test these.
WebTest is a Python package that does functional testing. With WebTest
you can write tests which simulate a full HTTP request against a WSGI
application, then test the information in the response. For speed
purposes, WebTest skips the setup/teardown of an actual HTTP server,
providing tests that run fast enough to be part of TDD.
Objectives
==========
- Write a test which checks the contents of the returned HTML
Steps
=====
#. First we copy the results of the previous step, as well as install
   the ``webtest`` package:
   .. code-block:: bash
    (env27)$ cd ..; cp -r unit_testing functional_testing; cd functional_testing
    (env27)$ python setup.py develop
    (env27)$ easy_install webtest
#. Let's extend ``unit_testing/tutorial/tests.py`` to include a
   functional test:
   .. literalinclude:: functional_testing/tutorial/tests.py
    :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
    .
    ----------------------------------------------------------------------
    Ran 2 tests in 0.141s
    OK
Analysis
========
We now have the end-to-end testing we were looking for. WebTest lets us
simply extend our existing ``nose``-based test approach with functional
tests that are reported in the same output. These new tests not only
cover our templating, but they didn't dramatically increase the
execution time of our tests.
Extra Credit
============
#. Why do our functional tests use ``b''``?
docs/quick_tutorial/functional_testing/development.ini
New file
@@ -0,0 +1,40 @@
[app:main]
use = egg:tutorial
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/functional_testing/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/functional_testing/tutorial/__init__.py
New file
@@ -0,0 +1,13 @@
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
    return Response('<body><h1>Hello World!</h1></body>')
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('hello', '/')
    config.add_view(hello_world, route_name='hello')
    return config.make_wsgi_app()
docs/quick_tutorial/functional_testing/tutorial/tests.py
New file
@@ -0,0 +1,31 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_hello_world(self):
        from tutorial import hello_world
        request = testing.DummyRequest()
        response = hello_world(request)
        self.assertEqual(response.status_code, 200)
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_hello_world(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hello World!</h1>', res.body)
docs/quick_tutorial/hello_world.rst
New file
@@ -0,0 +1,111 @@
================================
01: Single-File Web Applications
================================
What's the simplest way to get started in Pyramid? A single-file module.
No Python packages, no ``setup.py``, no other machinery.
Background
==========
Microframeworks are all the rage these days. "Microframework" is a
marketing term, not a technical one.  They have a low mental overhead:
they do so little, the only things you have to worry about are *your
things*.
Pyramid is special because it can act as a single-file module
microframework. You can have a single Python file that can be executed
directly by Python. But Pyramid also provides facilities to scale to
the largest of applications.
Python has a standard called :term:`WSGI` that defines how
Python web applications plug into standard servers, getting passed
incoming requests and returning responses. Most modern Python web
frameworks obey an "MVC" (model-view-controller) application pattern,
where the data in the model has a view that mediates interaction with
outside systems.
In this step we'll see a brief glimpse of WSGI servers, WSGI
applications, requests, responses, and views.
Objectives
==========
- Get a running Pyramid web application, as simply as possible
- Use that as a well-understood base for adding each unit of complexity
- Initial exposure to WSGI apps, requests, views, and responses
Steps
=====
#. Make sure you have followed the steps in :doc:`python_setup`.
#. Create a directory for this step:
   .. code-block:: bash
    (env27)$ mkdir hello_world; cd hello_world
#. Copy the following into ``hello_world/app.py``:
   .. literalinclude:: hello_world/app.py
    :linenos:
#. Run the application:
   .. code-block:: bash
    (env27)$ python app.py
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
New to Python web programming? If so, some lines in module merit
explanation:
#. *Line 11*. The ``if __name__ == '__main__':`` is Python's way of
   saying "Start here when running from the command line".
#. *Lines 12-14*. Use Pyramid's :term:`pyramid:configurator` to connect
   :term:`pyramid:view` code to a particular URL
   :term:`pyramid:route`.
#. *Lines 6-7*. Implement the view code that generates the
   :term:`pyramid:response`.
#. *Lines 15-17*. Publish a :term:`pyramid:WSGI` app using an HTTP
   server.
As shown in this example, the :term:`pyramid:configurator` plays a
central role in Pyramid development. Building an application from
loosely-coupled parts via :ref:`pyramid:configuration_narr` is a
central idea in Pyramid, one that we will revisit regularly in this
*Quick Tour*.
Extra Credit
============
#. Why do we do this:
   .. code-block:: python
      print ('Starting up server on http://localhost:6547')
   ...instead of:
   .. code-block:: python
      print 'Starting up server on http://localhost:6547'
#. What happens if you return a string of HTML? A sequence of integers?
#. Put something invalid, such as ``print xyz``, in the view function.
   Kill your ``python app.py`` with ``cntrl-c`` and restart,
   then reload your browser. See the exception in the console?
#. The ``GI`` in ``WSGI`` stands for "Gateway Interface". What web
   standard is this modelled after?
docs/quick_tutorial/hello_world/app.py
New file
@@ -0,0 +1,17 @@
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
    print ('Incoming request')
    return Response('<body><h1>Hello World!</h1></body>')
if __name__ == '__main__':
    config = Configurator()
    config.add_route('hello', '/')
    config.add_view(hello_world, route_name='hello')
    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 6543, app)
    server.serve_forever()
docs/quick_tutorial/index.rst
New file
@@ -0,0 +1,51 @@
.. _quick_tutorial:
==========================
Quick Tutorial for Pyramid
==========================
Pyramid is a web framework for Python 2 and 3. This tutorial gives a
Python 2/3-compatible, 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 experts of the Pyramid web framework.
Contents
========
.. toctree::
    :maxdepth: 1
    python_setup
    pyramid_setup
    tutorial_approach
    scaffolds
    hello_world
    package
    ini
    debugtoolbar
    unit_testing
    functional_testing
    views
    templating
    view_classes
    request_response
    routing
    jinja2
    static_assets
    json
    more_view_classes
    logging
    sessions
    forms
    databases
    authentication
    authorization
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
docs/quick_tutorial/ini.rst
New file
@@ -0,0 +1,144 @@
=================================================
03: Application Configuration with ``.ini`` Files
=================================================
Use Pyramid's ``pserve`` command with a ``.ini`` configuration file for
simpler, better application running.
Background
==========
Pyramid has a first-class concept of
:ref:`configuration <pyramid:configuration_narr>` distinct from code.
This approach is optional, but its presence makes it distinct from
other Python web frameworks. It taps into Python's ``setuptools``
library, which establishes conventions for how Python projects can be
installed and provide "entry points". Pyramid uses an entry point to
let a Pyramid application it where to find the WSGI app.
Objectives
==========
- Modify our ``setup.py`` to have an entry point telling Pyramid the
  location of the WSGI app
- Create an application driven by a ``.ini`` file
- Startup the application with Pyramid's ``pserve`` command
- Move code into the package's ``__init__.py``
Steps
=====
#. First we copy the results of the previous step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r package ini; cd ini
#. Our ``ini/setup.py`` needs a setuptools "entry point" in the
   ``setup()`` function:
   .. literalinclude:: ini/setup.py
    :linenos:
#. We can now install our project, thus generating (or re-generating) an
    "egg" at ``ini/tutorial.egg-info``:
   .. code-block:: bash
    (env27)$ python setup.py develop
#. Let's make a file ``ini/development.ini`` for our configuration:
   .. literalinclude:: ini/development.ini
    :language: ini
    :linenos:
#. We can refactor our startup code from the previous step's ``app.py``
   into ``ini/tutorial/__init__.py``:
   .. literalinclude:: ini/tutorial/__init__.py
    :linenos:
#. Now that ``ini/tutorial/app.py`` isn't used, let's remove it:
   .. code-block:: bash
    (env27)$ rm tutorial/app.py
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/``.
Analysis
========
Our ``development.ini`` file is read by ``pserve`` and serves to
bootstrap our application. Processing then proceeds as described in
the Pyramid chapter on
:ref:`application startup <pyramid:startup_chapter>`:
- ``pserve`` looks for ``[app:main]`` and finds ``use = egg:tutorial``
- The projects's ``setup.py`` has defined an "entry point" (lines 9-10)
  for the project  "main" entry point of ``tutorial:main``
- The ``tutorial`` package's ``__init__`` has a ``main`` function
- This function is invoked, with the values from certain ``.ini``
  sections passed in
The ``.ini`` file is also used for two other functions:
- *Choice of WSGI server*. ``[server:main]`` wires up the choice of WSGI
  *server* for your WSGI *application*. In this case, we are using
  ``wsgiref`` bundled in the Python library.
- *Python logging*. Pyramid uses Python standard logging, which needs a
  number of configuration values. The ``.ini`` serves this function.
  This provides the console log output that you see on startup and each
  request.
- *Port number*. ``port = 6543`` tells ``wsgiref`` to listen on port
  6543.
We moved our startup code from ``app.py`` to the package's
``tutorial/__init__.py``. This isn't necessary,
but it is a common style in Pyramid to take the WSGI app bootstrapping
out of your module's code and put it in the package's ``__init__.py``.
The ``pserve`` application runner has a number of command-line arguments
and options. We are using ``--reload`` which tells ``pserve`` to watch
the filesystem for changes to relevant code (Python files, the INI file,
etc.) and, when something changes, restart the application. Very handy
during development.
Extra Credit
============
#. If you don't like configuration and/or ``.ini`` files,
   could you do this yourself in Python code?
#. Can we have multiple ``.ini`` configuration files for a project? Why
   might you want to do that?
#. The entry point in ``setup.py`` didn't mention ``__init__.py`` when
   it the ``main`` function. Why not?
.. seealso::
   :ref:`pyramid:project_narr`,
   :ref:`pyramid:scaffolding_chapter`,
   :ref:`pyramid:what_is_this_pserve_thing`,
   :ref:`pyramid:environment_chapter`,
   :ref:`pyramid:paste_chapter`
Extra Credit
============
#. What is the purpose of ``**settings``? What does the ``**`` signify?
docs/quick_tutorial/ini/development.ini
New file
@@ -0,0 +1,38 @@
[app:main]
use = egg:tutorial
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/ini/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/ini/tutorial/__init__.py
New file
@@ -0,0 +1,13 @@
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
    return Response('<body><h1>Hello World!</h1></body>')
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('hello', '/')
    config.add_view(hello_world, route_name='hello')
    return config.make_wsgi_app()
docs/quick_tutorial/jinja2.rst
New file
@@ -0,0 +1,96 @@
==============================
12: Templating With ``jinja2``
==============================
We just said Pyramid doesn't prefer one templating language over
another. Time to prove it. Jinja2 is a popular templating system,
used in Flask and modelled after Django's templates. Let's add
``pyramid_jinja2``, a Pyramid :term:`add-on` which enables Jinja2 as a
:term:`renderer` in our Pyramid applications.
Objectives
==========
- Show Pyramid's support for different templating systems
- Learn about installing Pyramid add-ons
Steps
=====
#. In this step let's start by installing the ``pyramid_jinja2``
   add-on, the copying the ``view_class`` step's directory:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes jinja2; cd jinja2
    (env27)$ python setup.py develop
    (env27)$ easy_install pyramid_jinja2
#. We need to add an item to ``pyramid.includes`` in
   ``jinja2/development.ini``:
   .. literalinclude:: jinja2/development.ini
    :language: ini
    :linenos:
#. Our ``jinja2/tutorial/views.py`` simply changes its ``renderer``:
   .. literalinclude:: jinja2/tutorial/views.py
    :linenos:
#. Add ``jinja2/tutorial/home.jinja2`` as a template:
   .. literalinclude:: jinja2/tutorial/home.jinja2
    :language: html
#. Get the ``pyramid.includes`` into the functional test setup in
   ``jinja2/tutorial/tests.py``:
   .. literalinclude:: jinja2/tutorial/tests.py
    :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
Getting a Pyramid add-on into Pyramid is simple. First you use normal
Python package installation tools to install the add-on package into
your Python. You then tell Pyramid's configurator to run the setup code
in the add-on. In this case the setup code told Pyramid to make a new
"renderer" available that looked for ``.jinja2`` file extensions.
Our view code stayed largely the same. We simply changed the file
extension on the renderer. For the template, the syntax for Chameleon
and Jinja2's basic variable insertion is very similar.
Our functional tests don't have ``development.ini`` so they needed the
``pyramid.includes`` to be setup in the test setup.
Extra Credit
============
#. Our project now depends on ``pyramid_jinja2``. We installed that
   dependency manually. What is another way we could have made the
   association?
#. We used ``development.ini`` to get the :term:`configurator` to
   load ``pyramid_jinja2``'s configuration. What is another way could
   include it into the config?
.. seealso:: `Jinja2 homepage <http://jinja.pocoo.org/>`_,
   and
   :ref:`pyramid_jinja2 Overview <jinja2:overview>`
docs/quick_tutorial/jinja2/development.ini
New file
@@ -0,0 +1,42 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
    pyramid_jinja2
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/jinja2/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/jinja2/tutorial/__init__.py
New file
@@ -0,0 +1,9 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/jinja2/tutorial/home.jinja2
New file
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: {{ name }}</title>
</head>
<body>
<h1>Hi {{ name }}</h1>
</body>
</html>
docs/quick_tutorial/jinja2/tutorial/home.pt
New file
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Hi ${name}</h1>
</body>
</html>
docs/quick_tutorial/jinja2/tutorial/tests.py
New file
@@ -0,0 +1,50 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        settings = {
            'pyramid.includes': [
                'pyramid_jinja2'
            ]
        }
        app = main({}, **settings)
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/jinja2/tutorial/views.py
New file
@@ -0,0 +1,18 @@
from pyramid.view import (
    view_config,
    view_defaults
    )
@view_defaults(renderer='home.jinja2')
class TutorialViews:
    def __init__(self, request):
        self.request = request
    @view_config(route_name='home')
    def home(self):
        return {'name': 'Home View'}
    @view_config(route_name='hello')
    def hello(self):
        return {'name': 'Hello View'}
docs/quick_tutorial/json.rst
New file
@@ -0,0 +1,101 @@
========================================
14: Ajax Development With JSON Renderers
========================================
Modern web apps are more than rendered HTML. Dynamic pages now use
JavaScript to update the UI in the browser by requesting server data as
JSON. Pyramid supports this with a *JSON renderer*.
Background
==========
As we saw in :doc:`templating`, view declarations can specify a
renderer. Output from the view is then run through the renderer,
which generates and returns the ``Response``. We first used a Chameleon
renderer, then a Jinja2 renderer.
Renderers aren't limited, however, to templates that generate HTML.
Pyramid supplies a JSON renderer which takes Python data,
serializes it to JSON, and performs some other functions such as
setting the content type. In fact, you can write your own renderer (or
extend a built-in renderer) containing custom logic for your unique
application.
Steps
=====
#. First we copy the results of the ``view_classes`` step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes json; cd json
    (env27)$ python setup.py develop
#. We add a new route for ``hello_json`` in
   ``json/tutorial/__init__.py``:
   .. literalinclude:: json/tutorial/__init__.py
    :linenos:
#. Rather than implement a new view, we will "stack" another decorator
   on the ``hello`` view:
   .. literalinclude:: json/tutorial/views.py
    :linenos:
#. We need a new functional test at the end of
   ``json/tutorial/tests.py``:
   .. literalinclude:: json/tutorial/tests.py
    :linenos:
#. Run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/howdy.json`` in your browser and you
   will see the resulting JSON response.
Analysis
========
Earlier we changed our view functions and methods to return Python
data. This change to a data-oriented view layer made test writing
easier, decoupling the templating from the view logic.
Since Pyramid has a JSON renderer as well as the templating renderers,
it is an easy step to return JSON. In this case we kept the exact same
view and arranged to return a JSON encoding of the view data. We did
this by:
- Adding a route to map ``/howdy.json`` to a route name
- Providing a ``@view_config`` that associated that route name with an
  existing view
- *overriding* the view defaults in the view config that mentions the
  ``hello_json`` route, so that when the route is matched, we use the JSON
  renderer rather than the ``home.pt`` template renderer that would otherwise
  be used.
In fact, for pure Ajax-style web applications, we could re-use the existing
route by using Pyramid's view predicates to match on the
``Accepts:`` header sent by modern Ajax implementation.
Pyramid's JSON renderer uses the base Python JSON encoder,
thus inheriting its strengths and weaknesses. For example,
Python can't natively JSON encode DateTime objects. There are a number
of solutions for this in Pyramid, including extending the JSON renderer
with a custom renderer.
.. seealso:: :ref:`pyramid:views_which_use_a_renderer`,
   :ref:`pyramid:json_renderer`, and
   :ref:`pyramid:adding_and_overriding_renderers`
docs/quick_tutorial/json/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/json/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/json/tutorial/__init__.py
New file
@@ -0,0 +1,10 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.add_route('hello_json', 'howdy.json')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/json/tutorial/home.pt
New file
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Hi ${name}</h1>
</body>
</html>
docs/quick_tutorial/json/tutorial/tests.py
New file
@@ -0,0 +1,50 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
    def test_hello_json(self):
        res = self.testapp.get('/howdy.json', status=200)
        self.assertIn(b'{"name": "Hello View"}', res.body)
        self.assertEqual(res.content_type, 'application/json')
docs/quick_tutorial/json/tutorial/views.py
New file
@@ -0,0 +1,19 @@
from pyramid.view import (
    view_config,
    view_defaults
    )
@view_defaults(renderer='home.pt')
class TutorialViews:
    def __init__(self, request):
        self.request = request
    @view_config(route_name='home')
    def home(self):
        return {'name': 'Home View'}
    @view_config(route_name='hello')
    @view_config(route_name='hello_json', renderer='json')
    def hello(self):
        return {'name': 'Hello View'}
docs/quick_tutorial/logging.rst
New file
@@ -0,0 +1,77 @@
============================================
16: Collecting Application Info With Logging
============================================
Capture debugging and error output from your web applications using
standard Python logging.
Background
==========
It's important to know what is going on inside our web application.
In development we might need to collect some output. In production,
we might need to detect problems when other people use the site. We
need *logging*.
Fortunately Pyramid uses the normal Python approach to logging. The
scaffold generated, in your ``development.ini``, a number of lines that
configure the logging for you to some reasonable defaults. You then see
messages sent by Pyramid (for example, when a new request comes in.)
Objectives
==========
- Inspect the configuration setup used for logging
- Add logging statements to your view code
Steps
=====
#. First we copy the results of the ``view_classes`` step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes logging; cd logging
    (env27)$ python setup.py develop
#. Extend ``logging/tutorial/views.py`` to log a message:
   .. literalinclude:: logging/tutorial/views.py
    :linenos:
#. Make sure the tests still pass:
   .. code-block:: bash
    (env27)$ nosetests tutorial
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` and ``http://localhost:6543/howdy``
   in your browser. Note, both in the console and in the debug
   toolbar, the message that you logged.
Analysis
========
Our ``development.ini`` configuration file wires up Python standard
logging for our Pyramid application:
.. literalinclude:: logging/development.ini
    :language: ini
In this, our ``tutorial`` Python package is setup as a logger
and configured to log messages at a ``DEBUG`` or higher level. When you
visit ``http://localhost:6543`` your console will now show::
 2013-08-09 10:42:42,968 DEBUG [tutorial.views][MainThread] In home view
Also, if you have configured your Pyramid application to use the
``pyramid_debugtoolbar``, logging statements appear in one of its menus.
.. seealso:: See Also: :ref:`pyramid:logging_chapter`
docs/quick_tutorial/logging/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/logging/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/logging/tutorial/__init__.py
New file
@@ -0,0 +1,9 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/logging/tutorial/home.pt
New file
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Hi ${name}</h1>
</body>
</html>
docs/quick_tutorial/logging/tutorial/tests.py
New file
@@ -0,0 +1,44 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/logging/tutorial/views.py
New file
@@ -0,0 +1,23 @@
import logging
log = logging.getLogger(__name__)
from pyramid.view import (
    view_config,
    view_defaults
    )
@view_defaults(renderer='home.pt')
class TutorialViews:
    def __init__(self, request):
        self.request = request
    @view_config(route_name='home')
    def home(self):
        log.debug('In home view')
        return {'name': 'Home View'}
    @view_config(route_name='hello')
    def hello(self):
        log.debug('In hello view')
        return {'name': 'Hello View'}
docs/quick_tutorial/more_view_classes.rst
New file
@@ -0,0 +1,180 @@
==========================
15: More With View Classes
==========================
Group views into a class, sharing configuration, state, and logic.
Background
==========
As part of its mission to help build more ambitious web applications,
Pyramid provides many more features for views and view classes.
The Pyramid documentation discusses views as a Python "callable". This
callable can be a function, an object with an ``__call__``,
or a Python class. In this last case, methods on the class can be
decorated with ``@view_config`` to register the class methods with the
:term:`configurator` as a view.
So far our views have been simple, free-standing functions. Many times
your views are related: different ways to look at or work on the same
data or a REST API that handles multiple operations. Grouping these
together as a
:ref:`view class <pyramid:class_as_view>` makes sense:
- Group views
- Centralize some repetitive defaults
- Share some state and helpers
Pyramid views have
:ref:`view predicates <pyramid:view_configuration_parameters>` that
help determine which view is matched to a request. These predicates
provide many axes of flexibility.
The following shows a simple example with four operations operations:
view a home page which leads to a form, save a change,
and press the delete button.
Objectives
==========
- Group related views into a view class
- Centralize configuration with class-level ``@view_defaults``
- Dispatch one route/URL to multiple views based on request data
- Share stated and logic between views and templates via the view class
Steps
=====
#. First we copy the results of the previous step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r templating more_view_classes; cd more_view_classes
    (env27)$ python setup.py develop
#. Our route in ``more_view_classes/tutorial/__init__.py`` needs some
   replacement patterns:
   .. literalinclude:: more_view_classes/tutorial/__init__.py
    :linenos:
#. Our ``more_view_classes/tutorial/views.py`` now has a view class with
   several views:
   .. literalinclude:: more_view_classes/tutorial/views.py
    :linenos:
#. Our primary view needs a template at
   ``more_view_classes/tutorial/home.pt``:
   .. literalinclude:: more_view_classes/tutorial/home.pt
    :language: html
#. Ditto for our other view from the previous section at
   ``more_view_classes/tutorial/hello.pt``:
   .. literalinclude:: more_view_classes/tutorial/hello.pt
    :language: html
#. We have an edit view that also needs a template at
   ``more_view_classes/tutorial/edit.pt``:
   .. literalinclude:: more_view_classes/tutorial/edit.pt
    :language: html
#. And finally the delete view's template at
   ``more_view_classes/tutorial/delete.pt``:
   .. literalinclude:: more_view_classes/tutorial/delete.pt
    :language: html
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/howdy/jane/doe`` in your browser. Click
   the ``Save`` and ``Delete`` buttons and watch the output in the
   console window.
Analysis
========
As you can see, the four views are logically grouped together.
Specifically:
- We have a ``home`` view available at ``http://localhost:6543/`` with
  a clickable link to the ``hello`` view.
- The second view is returned when you go to ``/howdy/jane/doe``. This
  URL is
  mapped to the ``hello`` route that we centrally set using the optional
  ``@view_defaults``.
- The third view is returned when the form is submitted with a ``POST``
  method. This rule is specified in the ``@view_config`` for that view.
- The fourth view is returned when clicking on a button such
  as ``<input type="submit" name="form.delete" value="Delete"/>``.
In this step we show using the following information as criteria to
decide which view to use:
- Method of the HTTP request (``GET``, ``POST``, etc.)
- Parameter information in the request (submitted form field names)
We also centralize part of the view configuration to the class level
with ``@view_defaults``, then in one view, override that default just
for that one view. Finally, we put this commonality between views to
work in the view class by sharing:
- State assigned in ``TutorialViews.__init__``
- A computed value
These are then available both in the view methods but also in the
templates (e.g. ``${view.view_name}`` and ``${view.full_name}``.
As a note, we made a switch in our templates on how we generate URLs.
We previously hardcode the URLs, such as::
  <a href="/howdy/jane/doe">Howdy</a>
In ``home.pt`` we switched to::
  <a href="${request.route_url('hello', first='jane',
        last='doe')}">form</a>
Pyramid has rich facilities to help generate URLs in a flexible,
non-error-prone fashion.
Extra Credit
============
#. Why could our template do ``${view.full_name}`` and not have to do
   ``${view.full_name()}``?
#. The ``edit`` and ``delete`` views are both submitted to with
   ``POST``. Why does the ``edit`` view configuration not catch the
   the ``POST`` used by ``delete``?
#. We used Python ``@property`` on ``full_name``. If we reference this
   many times in a template or view code, it would re-compute this
   every time. Does Pyramid provide something that will cache the initial
   computation on a property?
#. Can you associate more than one route with the same view?
#. There is also a ``request.route_path`` API.  How does this differ from
   ``request.route_url``?
.. seealso:: :ref:`pyramid:class_as_view`, `Weird Stuff You Can Do With
   URL Dispatch <http://www.plope.com/weird_pyramid_urldispatch>`_
docs/quick_tutorial/more_view_classes/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/more_view_classes/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/more_view_classes/tutorial/__init__.py
New file
@@ -0,0 +1,9 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy/{first}/{last}')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/more_view_classes/tutorial/delete.pt
New file
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${page_title}</title>
</head>
<body>
<h1>${view.view_name} - ${page_title}</h1>
</body>
</html>
docs/quick_tutorial/more_view_classes/tutorial/edit.pt
New file
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${view.view_name} - ${page_title}</title>
</head>
<body>
<h1>${view.view_name} - ${page_title}</h1>
<p>You submitted <code>${new_name}</code></p>
</body>
</html>
docs/quick_tutorial/more_view_classes/tutorial/hello.pt
New file
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${view.view_name} - ${page_title}</title>
</head>
<body>
<h1>${view.view_name} - ${page_title}</h1>
<p>Welcome, ${view.full_name}</p>
<form method="POST"
      action="${request.current_route_url()}">
    <input name="new_name"/>
    <input type="submit" name="form.edit" value="Save"/>
    <input type="submit" name="form.delete" value="Delete"/>
</form>
</body>
</html>
docs/quick_tutorial/more_view_classes/tutorial/home.pt
New file
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${view.view_name} - ${page_title}</title>
</head>
<body>
<h1>${view.view_name} - ${page_title}</h1>
<p>Go to the <a href="${request.route_url('hello', first='jane',
        last='doe')}">form</a>.</p>
</body>
</html>
docs/quick_tutorial/more_view_classes/tutorial/tests.py
New file
@@ -0,0 +1,31 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['page_title'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'TutorialViews - Home View', res.body)
docs/quick_tutorial/more_view_classes/tutorial/views.py
New file
@@ -0,0 +1,39 @@
from pyramid.view import (
    view_config,
    view_defaults
    )
@view_defaults(route_name='hello')
class TutorialViews:
    def __init__(self, request):
        self.request = request
        self.view_name = 'TutorialViews'
    @property
    def full_name(self):
        first = self.request.matchdict['first']
        last = self.request.matchdict['last']
        return first + ' ' + last
    @view_config(route_name='home', renderer='home.pt')
    def home(self):
        return {'page_title': 'Home View'}
    # Retrieving /howdy/first/last the first time
    @view_config(renderer='hello.pt')
    def hello(self):
        return {'page_title': 'Hello View'}
    # Posting to /home via the "Edit" submit button
    @view_config(request_method='POST', renderer='edit.pt')
    def edit(self):
        new_name = self.request.params['new_name']
        return {'page_title': 'Edit View', 'new_name': new_name}
    # Posting to /home via the "Delete" submit button
    @view_config(request_param='form.delete', renderer='delete.pt')
    def delete(self):
        print ('Deleted')
        return {'page_title': 'Delete View'}
docs/quick_tutorial/package.rst
New file
@@ -0,0 +1,112 @@
============================================
02: Python Packages for Pyramid Applications
============================================
Most modern Python development is done using Python packages, an approach
Pyramid puts to good use. In this step we re-do "Hello World" as a
minimum Python package inside a minimum Python project.
Background
==========
Python developers can organize a collection of modules and files into a
namespaced unit called a :ref:`package <python:tut-packages>`. If a
directory is on ``sys.path`` and has a special file named
``__init__.py``, it is treated as a Python package.
Packages can be bundled up, made available for installation,
and installed through a (muddled, but improving) toolchain oriented
around a ``setup.py`` file for a
`setuptools project <http://pythonhosted.org/setuptools/setuptools.html>`_.
Explaining it all in this
tutorial will induce madness. For this tutorial, this is all you need to
know:
- We will have a directory for each tutorial step as a
  setuptools *project*
- This project will contain a ``setup.py`` which injects the features
  of the setuptool's project machinery into the directory
- In this project we will make a ``tutorial`` subdirectory into a Python
  *package* using an ``__init__.py`` Python module file
- We will run ``python setup.py develop`` to install our project in
  development mode
In summary:
- You'll do your development in a Python *package*
- That package will be part of a setuptools *project*
Objectives
==========
- Make a Python "package" directory with an ``__init__.py``
- Get a minimum Python "project" in place by making a ``setup.py``
- Install our ``tutorial`` project in development mode
Steps
=====
#. Make an area for this tutorial step:
   .. code-block:: bash
    (env27)$ cd ..; mkdir package; cd package
#. In ``package/setup.py``, enter the following:
   .. literalinclude:: package/setup.py
#. Make the new project installed for development then make a directory
   for the actual code:
   .. code-block:: bash
    (env27)$ python setup.py develop
    (env27)$ mkdir tutorial
#. Enter the following into ``package/tutorial/__init__.py``:
   .. literalinclude:: package/tutorial/__init__.py
#. Enter the following into ``package/tutorial/app.py``:
   .. literalinclude:: package/tutorial/app.py
#. Run the WSGI application with:
   .. code-block:: bash
    (env27)$ python tutorial/app.py
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
Python packages give us an organized unit of project development.
Python projects, via ``setup.py``, gives us special features when
our package is installed (in this case, in local development mode.)
In this step we have a Python package called ``tutorial``. We use the
same name in each step of the tutorial, to avoid unnecessary re-typing.
Above this ``tutorial`` directory we have the files that handle the
packaging of this, well, package. At the moment, all we need is a
bare-bones ``ini/setup.py``.
Everything else is the same about our application. We simply made a
Python package with a ``setup.py`` and installed it in development mode.
Note that the way we're running the app (``python tutorial/app.py``) is a bit
of an odd duck.  We would never do this unless we were writing a tutorial that
tries to capture how this stuff works a step at a time.  It's generally a bad
idea to run a Python module inside a package directly as a script.
.. seealso:: :ref:`Python Packages <python:tut-packages>`,
   `setuptools Entry Points <http://pythonhosted.org/setuptools/pkg_resources.html#entry-points>`_
docs/quick_tutorial/package/setup.py
New file
@@ -0,0 +1,9 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
)
docs/quick_tutorial/package/tutorial/__init__.py
New file
@@ -0,0 +1 @@
# package
docs/quick_tutorial/package/tutorial/app.py
New file
@@ -0,0 +1,17 @@
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
    print ('Incoming request')
    return Response('<body><h1>Hello World!</h1></body>')
if __name__ == '__main__':
    config = Configurator()
    config.add_route('hello', '/')
    config.add_view(hello_world, route_name='hello')
    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 6543, app)
    server.serve_forever()
docs/quick_tutorial/pyramid_setup.rst
New file
@@ -0,0 +1,27 @@
=============
Pyramid Setup
=============
Installing Pyramid is easy and normal from a Python packaging
perspective. Again, *make sure* you have your virtual environment first
in your path using ``source bin/activate``.
.. code-block:: bash
  (env27)$ easy_install pyramid
  ....chuggalugga...
  (env27ß)$ which pserve
You now have Pyramid installed. The second command confirms this by
looking for the Pyramid ``pserve`` command that should be on your
``$PATH`` in the ``bin`` of your virtual environment.
Installing Everything
=====================
Later parts of the tutorial install more packages. Most likely,
you'd like to go ahead and get much of it now:
.. code-block:: bash
  (env27)$ easy_install pyramid nose webtest deform sqlalchemy
docs/quick_tutorial/python_setup.rst
New file
@@ -0,0 +1,88 @@
============
Python Setup
============
First things first: we need our Python environment in ship-shape.
Pyramid encourages standard Python development practices (virtual
environments, packaging tools, logging, etc.) so let's get our working
area in place.
.. note::
    This tutorial is aimed at Python 2.7. It also works with
    Python 3.3.
*This step has most likely been performed already on the CCDC computers.*
Prequisites
===========
Modern Python development means two tools to add to the standard
Python installation: packaging and virtual environments.
Python's tools for installing packages is undergoing rapid change. For
this tutorial, we will install the latest version of
`setuptools <https://pypi.python.org/pypi/setuptools/>`_. This gives us
the ``easy_install`` command-line tool for installing Python packages.
Presuming you have Python on your ``PATH``:
.. code-block:: bash
  $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python
We now have an ``easy_install`` command that we can use to install
``virtualenv``:
.. code-block:: bash
  $ easy_install virtualenv
Making a Virtual Environment
============================
Developing in isolation helps us ensure what we are learning doesn't
conflict with any packages installed from other work on the machine.
*Virtual environments* let us do just this.
Presuming you have made a tutorial area at some location (referenced as
``your/tutorial/directory`` below):
.. code-block:: bash
  $ cd your/tutorial/directory
  $ virtualenv env27
  $ source env27/bin/activate
  (env27)$ which python2.7
Once you do this, your path will be setup to point at the ``bin`` of
your virtual environment. Your prompt will also change, as noted above.
.. note::
    This tutorial presumes you are working in a command-line shell
    which has performed the ``source env27/bin/activate``. If you
    close that shell, or open a new one, you will need to re-perform
    that command.
Discussion
==========
The modern world of Python packaging eschews ``easy_install`` in favor
of ``pip``, a more-recent and maintained packaging tool. Why doesn't
this tutorial use it?
- ``pip`` is only gradually getting the ability to install Windows
  binaries. ``easy_install`` has been able to do this for years.
- Until recently, ``pip`` has not been able to use "namespace
  packages." As the ``pip`` support for this stabilizes,
  we can switch to using ``pip``.
- You have to use ``easy_install`` to get ``pip`` installed, so why not
  just stick with ``easy_install``.
Python 3.3 has a `built-in story for virtual
environments <http://docs.python.org/dev/library/venv.html>`_. This
eliminates the requirement for installing ``virtualenv``. Instead,
Python 3.3 provides the ``pyvenv`` command for creating virtual
environments.
docs/quick_tutorial/request_response.rst
New file
@@ -0,0 +1,101 @@
=======================================
10: Handling Web Requests and Responses
=======================================
Web applications handle incoming requests and return outgoing responses.
Pyramid makes working with requests and responses convenient and
reliable.
Objectives
==========
- Learn the background on Pyramid's choices for requests and responses
- Grab data out of the request
- Change information in the response headers
Background
==========
Developing for the web means processing web requests. As this is a
critical part of a web application, web developers need a robust,
mature set of software for web requests and returning web
responses.
Pyramid has always fit nicely into the existing world of Python web
development (virtual environments, packaging, scaffolding,
first to embrace Python 3, etc.) For request handling, Pyramid turned
to the well-regarded :term:`WebOb` Python library for request and
response handling. In our example
above, Pyramid hands ``hello_world`` a ``request`` that is
:ref:`based on WebOb <webob_chapter>`.
Steps
=====
#. First we copy the results of the ``view_classes`` step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes request_response; cd request_response
    (env27)$ python setup.py develop
#. Simplify the routes in ``request_response/tutorial/__init__.py``:
   .. literalinclude:: request_response/tutorial/__init__.py
#. We only need one view in ``request_response/tutorial/views.py``:
   .. literalinclude:: request_response/tutorial/views.py
#. Update the tests in ``request_response/tutorial/tests.py``:
   .. literalinclude:: request_response/tutorial/tests.py
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser. You will be
   redirected to ``http://localhost:6543/plain``
#. Open ``http://localhost:6543/plain?name=alice`` in your browser.
Analysis
========
In this view class we have two routes and two views, with the first
leading to the second by an HTTP redirect. Pyramid can
:ref:`generate redirects <pyramid:http_redirect>` by returning a
special object from a view or raising a special exception.
In this Pyramid view, we get the URL being visited from ``request.url``.
Also, if you visited ``http://localhost:6543/plain?name=alice``,
the name is included in the body of the response::
  URL http://localhost:6543/plain?name=alice with name: alice
Finally, we set the response's content type and body, then return the
Response.
We updated the unit and functional tests to prove that our code
does the redirection, but also handles sending and not sending
``/plain?name``.
Extra Credit
============
#. Could we also ``raise HTTPFound(location='/plain')`` instead of
   returning it?  If so, what's the difference?
.. seealso:: :ref:`pyramid:webob_chapter`,
   :ref:`generate redirects <pyramid:http_redirect>`
docs/quick_tutorial/request_response/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/request_response/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/request_response/tutorial/__init__.py
New file
@@ -0,0 +1,9 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('plain', '/plain')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/request_response/tutorial/home.pt
New file
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Hi ${name}</h1>
</body>
</html>
docs/quick_tutorial/request_response/tutorial/tests.py
New file
@@ -0,0 +1,54 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual(response.status, '302 Found')
    def test_plain_without_name(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.plain()
        self.assertIn('No Name Provided', response.body)
    def test_plain_with_name(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        request.GET['name'] = 'Jane Doe'
        inst = TutorialViews(request)
        response = inst.plain()
        self.assertIn('Jane Doe', response.body)
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_plain_without_name(self):
        res = self.testapp.get('/plain', status=200)
        self.assertIn(b'No Name Provided', res.body)
    def test_plain_with_name(self):
        res = self.testapp.get('/plain?name=Jane%20Doe', status=200)
        self.assertIn(b'Jane Doe', res.body)
docs/quick_tutorial/request_response/tutorial/views.py
New file
@@ -0,0 +1,22 @@
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
from pyramid.view import view_config
class TutorialViews:
    def __init__(self, request):
        self.request = request
    @view_config(route_name='home')
    def home(self):
        return HTTPFound(location='/plain')
    @view_config(route_name='plain')
    def plain(self):
        name = self.request.params.get('name', 'No Name Provided')
        body = 'URL %s with name: %s' % (self.request.url, name)
        return Response(
            content_type='text/plain',
            body=body
        )
docs/quick_tutorial/rest_ajax.rst
New file
@@ -0,0 +1,62 @@
==================
29: REST with Ajax
==================
Use Ajax operations to talk to a REST interface.
Objectives
==========
- Populate a list with JSON data
- Update contents with client-side forms that post to REST operations
Steps
=====
#. We are going to use the previous step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r rest_ajax_layout rest_ajax; cd rest_ajax
    (env27)$ python setup.py develop
#. Let's first add a Javascript file that implements our browser-side
   logic and talks to the REST service:
#. Introduce ``pyramid_jinja2`` dependency in
   ``rest_ajax/tutorial/static/site.js``:
   .. literalinclude:: rest_ajax/tutorial/static/site.js
    :language: js
    :linenos:
#. Add a ``<script>`` reference to this at the bottom of
   ``rest_ajax/tutorial/templates/layout.jinja2``
   .. literalinclude:: rest_ajax/tutorial/templates/layout.jinja2
    :language: html
    :linenos:
#. Update ``rest_ajax/tutorial/templates/folder.jinja2`` to include a
   modal dialog:
   .. literalinclude:: rest_ajax/tutorial/templates/folder.jinja2
    :language: html
    :linenos:
#. Our views in ``rest_ajax/tutorial/views.py`` need to handle our
   REST operations:
   .. literalinclude:: rest_ajax/tutorial/views.py
      :linenos:
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser and
   add (+ button), edit (click link), and delete (click trash icon)
   items in the root folder.
docs/quick_tutorial/rest_ajax_layout.rst
New file
@@ -0,0 +1,50 @@
=========================
28: REST with Ajax Layout
=========================
Produce a grid-like UI to prepare for async REST operations.
Steps
=====
#. We are going to use the previous step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r rest_bootstrap rest_ajax_layout; cd rest_ajax_layout
    (env27)$ python setup.py develop
#. Get a new menu item for ``Folders`` in
   ``rest_ajax_layout/tutorial/templates/layout.jinja2``:
   .. literalinclude:: rest_ajax_layout/tutorial/templates/layout.jinja2
    :language: html
    :linenos:
#. In  ``rest_ajax_layout/tutorial/views.py``, add a JSON view and remove
   unused previous views:
   .. literalinclude:: rest_ajax_layout/tutorial/views.py
      :linenos:
#. Create a template at
   ``rest_ajax_layout/tutorial/templates/folder.jinja2``:
   .. literalinclude:: rest_ajax_layout/tutorial/templates/folder.jinja2
    :language: html
    :linenos:
#. Do some cleanup:
   .. code-block:: bash
    (env27)$ rm tutorial/templates/*.pt
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
docs/quick_tutorial/rest_bootstrap.rst
New file
@@ -0,0 +1,88 @@
=========================================
27: Beginning REST with Twitter Bootstrap
=========================================
Begin making a REST application by adding Twitter Bootstrap, jQuery,
and a common layout.
Objectives
==========
- Switch to Jinja2
- Make a "layout" that is shared between templates
- Introduce jQuery and Twitter Bootstrap as well as local static
  resources
Steps
=====
#. We are going to use the previous step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r traversal_zodb rest_bootstrap; cd rest_bootstrap
    (env27)$ mkdir tutorial/static; mkdir tutorial/templates
#. Introduce ``pyramid_jinja2`` dependency in
   ``rest_bootstrap/setup.py``:
   .. literalinclude:: rest_bootstrap/setup.py
      :linenos:
#. We can now install our project:
   .. code-block:: bash
    (env27)$ python setup.py develop
#. Modify our ``rest_bootstrap/development.ini`` to include
   ``pyramid_jinja2`` configuration:
   .. literalinclude:: rest_bootstrap/development.ini
      :language: ini
      :linenos:
#. Our startup code in ``rest_bootstrap/tutorial/__init__.py`` gets
   a static view:
   .. literalinclude:: rest_bootstrap/tutorial/__init__.py
      :linenos:
#. Our home view in ``rest_bootstrap/tutorial/views.py`` references
   a Jinja2 template:
   .. literalinclude:: rest_bootstrap/tutorial/views.py
      :linenos:
#. Our site template in
   ``rest_bootstrap/tutorial/templates/site.jinja2``
   references a master layout:
   .. literalinclude:: rest_bootstrap/tutorial/templates/site.jinja2
    :language: html
    :linenos:
#. Add the master layout template in
   ``rest_bootstrap/tutorial/templates/layout.jinja2``:
   .. literalinclude:: rest_bootstrap/tutorial/templates/layout.jinja2
    :language: html
    :linenos:
#. A small amount of stying in
   ``rest_bootstrap/tutorial/static/site.css``:
   .. literalinclude:: rest_bootstrap/tutorial/static/site.css
    :language: css
    :linenos:
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
docs/quick_tutorial/routing.rst
New file
@@ -0,0 +1,119 @@
==========================================
11: Dispatching URLs To Views With Routing
==========================================
Routing matches incoming URL patterns to view code. Pyramid's routing
has a number of useful features.
Background
==========
Writing web applications usually means sophisticated URL design. We
just saw some Pyramid machinery for requests and views. Let's look at
features that help in routing.
Previously we saw the basics of routing URLs to views in Pyramid:
- Your project's "setup" code registers a route name to be used when
  matching part of the URL
- Elsewhere, a view is configured to be called for that route name
.. note::
    Why do this twice? Other Python web frameworks let you create a
    route and associate it with a view in one step. As
    illustrated in :ref:`routes_need_ordering`, multiple routes might match the
    same URL pattern. Rather than provide ways to help guess, Pyramid lets you
    be explicit in ordering. Pyramid also gives facilities to avoid the
    problem.  It's relatively easy to build a system that uses implicit route
    ordering with Pyramid too.  See `The Groundhog series of screencasts
    <http://bfg.repoze.org/videos#groundhog1>`_ if you're interested in
    doing so.
Objectives
==========
- Define a route that extracts part of the URL into a Python dictionary
- Use that dictionary data in a view
Steps
=====
#. First we copy the results of the ``view_classes`` step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes routing; cd routing
    (env27)$ python setup.py develop
#. Our ``routing/tutorial/__init__.py`` needs a route with a replacement
   pattern:
   .. literalinclude:: routing/tutorial/__init__.py
    :linenos:
#. We just need one view in ``routing/tutorial/views.py``:
   .. literalinclude:: routing/tutorial/views.py
    :linenos:
#. We just need one view in ``routing/tutorial/home.pt``:
   .. literalinclude:: routing/tutorial/home.pt
    :language: html
    :linenos:
#. Update ``routing/tutorial/tests.py``:
   .. literalinclude:: routing/tutorial/tests.py
    :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/howdy/amy/smith`` in your browser.
Analysis
========
In ``__init__.py`` we see an important change in our route declaration:
.. code-block:: python
    config.add_route('hello', '/howdy/{first}/{last}')
With this we tell the :term:`pyramid:configurator` that our URL has
a "replacement pattern".  With this, URLs such as ``/howdy/amy/smith``
will assign ``amy`` to ``first`` and ``smith`` to ``last``. We can then
use this data in our view:
.. code-block:: python
    self.request.matchdict['first']
    self.request.matchdict['last']
``request.matchdict`` contains values from the URL that match the
"replacement patterns" (the curly braces) in the route declaration.
This information can then be used anywhere in Pyramid that has access
to the request.
Extra Credit
============
#. What happens if you to go the URL
   ``http://localhost:6543/howdy``? Is this the result that you
   expected?
.. seealso:: `Weird Stuff You Can Do With URL
   Dispatch <http://www.plope.com/weird_pyramid_urldispatch>`_
docs/quick_tutorial/routing/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/routing/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/routing/tutorial/__init__.py
New file
@@ -0,0 +1,8 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/howdy/{first}/{last}')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/routing/tutorial/home.pt
New file
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>${name}</h1>
<p>First: ${first}, Last: ${last}</p>
</body>
</html>
docs/quick_tutorial/routing/tutorial/tests.py
New file
@@ -0,0 +1,36 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        request.matchdict['first'] = 'First'
        request.matchdict['last'] = 'Last'
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual(response['first'], 'First')
        self.assertEqual(response['last'], 'Last')
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/howdy/Jane/Doe', status=200)
        self.assertIn(b'Jane', res.body)
        self.assertIn(b'Doe', res.body)
docs/quick_tutorial/routing/tutorial/views.py
New file
@@ -0,0 +1,20 @@
from pyramid.view import (
    view_config,
    view_defaults
    )
@view_defaults(renderer='home.pt')
class TutorialViews:
    def __init__(self, request):
        self.request = request
    @view_config(route_name='home')
    def home(self):
        first = self.request.matchdict['first']
        last = self.request.matchdict['last']
        return {
            'name': 'Home View',
            'first': first,
            'last': last
        }
docs/quick_tutorial/scaffolds.rst
New file
@@ -0,0 +1,84 @@
=============================================
Prelude: Quick Project Startup with Scaffolds
=============================================
To ease the process of getting started, Pyramid provides *scaffolds*
that generate sample projects from templates in Pyramid and Pyramid
add-ons.
Background
==========
We're going to cover a lot in this tutorial, focusing on one topic at a
time and writing everything from scratch. As a warmup, though,
it sure would be nice to see some pixels on a screen.
Like other web development frameworks, Pyramid provides a number of
"scaffolds" that generate working Python, template, and CSS code for
sample applications. In this step we'll use a built-in scaffold to let
us preview a Pyramid application, before starting from scratch on Step 1.
Objectives
==========
- Use Pyramid's ``pcreate`` command to list scaffolds and make a new
  project
- Start up a Pyramid application and visit it in a web browser
Steps
=====
#. Pyramid's ``pcreate`` command can list the available scaffolds:
    .. code-block:: bash
        (env27)$ pcreate --list
        Available scaffolds:
          alchemy:                 Pyramid SQLAlchemy project using url dispatch
          starter:                 Pyramid starter project
          zodb:                    Pyramid ZODB project using traversal
#. Tell ``pcreate`` to use the ``starter`` scaffold to make our project:
    .. code-block:: bash
        (env27)$ pcreate --scaffold starter scaffolds
#. Use normal Python development to setup our project for development:
    .. code-block:: bash
        (env27)$ cd scaffolds
        (env27)$ python setup.py develop
#. Startup the application by pointing Pyramid's ``pserve`` command at
   the project's (generated) configuration file:
    .. code-block:: bash
        (env27)$ pserve development.ini --reload
   On startup, ``pserve`` logs some output:
   .. code-block:: bash
     Starting subprocess with file monitor
     Starting server in PID 72213.
     Starting HTTP server on http://0.0.0.0:6543
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
Rather than starting from scratch, ``pcreate`` can make getting a
Python project containing a Pyramid application a quick matter.
Pyramid ships with a few scaffolds. But installing a Pyramid add-on can
give you new scaffolds from that add-on.
``pserve`` is Pyramid's application runner, separating operational
details from your code. When you install Pyramid, a small command
program called ``pserve`` is written to your ``bin`` directory. This
program is an executable Python module. It is passed a configuration
file (in this case, ``development.ini``.)
docs/quick_tutorial/scaffolds/CHANGES.txt
New file
@@ -0,0 +1,4 @@
0.0
---
-  Initial version
docs/quick_tutorial/scaffolds/MANIFEST.in
New file
@@ -0,0 +1,2 @@
include *.txt *.ini *.cfg *.rst
recursive-include scaffolds *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
docs/quick_tutorial/scaffolds/README.txt
New file
@@ -0,0 +1 @@
scaffolds README
docs/quick_tutorial/scaffolds/development.ini
New file
@@ -0,0 +1,60 @@
###
# app configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
use = egg:scaffolds
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
    pyramid_debugtoolbar
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
###
# wsgi server configuration
###
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
###
# logging configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
keys = root, scaffolds
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_scaffolds]
level = DEBUG
handlers =
qualname = scaffolds
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
docs/quick_tutorial/scaffolds/production.ini
New file
@@ -0,0 +1,54 @@
###
# app configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
use = egg:scaffolds
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
###
# wsgi server configuration
###
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
###
# logging configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
keys = root, scaffolds
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
[logger_scaffolds]
level = WARN
handlers =
qualname = scaffolds
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
docs/quick_tutorial/scaffolds/scaffolds/__init__.py
New file
@@ -0,0 +1,11 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    config = Configurator(settings=settings)
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('home', '/')
    config.scan()
    return config.make_wsgi_app()
docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico
docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png
docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png
docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css
New file
@@ -0,0 +1,8 @@
* html img,
* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none",
this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')",
this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''),
this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')",
this.runtimeStyle.backgroundImage = "none")),this.pngSet=true)
);}
#wrap{display:table;height:100%}
docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png
docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css
New file
@@ -0,0 +1,372 @@
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
{
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  font-size: 100%; /* 16px */
  vertical-align: baseline;
  background: transparent;
}
body
{
  line-height: 1;
}
ol, ul
{
  list-style: none;
}
blockquote, q
{
  quotes: none;
}
blockquote:before, blockquote:after, q:before, q:after
{
  content: '';
  content: none;
}
:focus
{
  outline: 0;
}
ins
{
  text-decoration: none;
}
del
{
  text-decoration: line-through;
}
table
{
  border-collapse: collapse;
  border-spacing: 0;
}
sub
{
  vertical-align: sub;
  font-size: smaller;
  line-height: normal;
}
sup
{
  vertical-align: super;
  font-size: smaller;
  line-height: normal;
}
ul, menu, dir
{
  display: block;
  list-style-type: disc;
  margin: 1em 0;
  padding-left: 40px;
}
ol
{
  display: block;
  list-style-type: decimal-leading-zero;
  margin: 1em 0;
  padding-left: 40px;
}
li
{
  display: list-item;
}
ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
{
  margin-top: 0;
  margin-bottom: 0;
}
ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
{
  list-style-type: circle;
}
ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
{
  list-style-type: square;
}
.hidden
{
  display: none;
}
p
{
  line-height: 1.5em;
}
h1
{
  font-size: 1.75em;
  line-height: 1.7em;
  font-family: helvetica, verdana;
}
h2
{
  font-size: 1.5em;
  line-height: 1.7em;
  font-family: helvetica, verdana;
}
h3
{
  font-size: 1.25em;
  line-height: 1.7em;
  font-family: helvetica, verdana;
}
h4
{
  font-size: 1em;
  line-height: 1.7em;
  font-family: helvetica, verdana;
}
html, body
{
  width: 100%;
  height: 100%;
}
body
{
  margin: 0;
  padding: 0;
  background-color: #fff;
  position: relative;
  font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
}
a
{
  color: #1b61d6;
  text-decoration: none;
}
a:hover
{
  color: #e88f00;
  text-decoration: underline;
}
body h1, body h2, body h3, body h4, body h5, body h6
{
  font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif;
  font-weight: 400;
  color: #373839;
  font-style: normal;
}
#wrap
{
  min-height: 100%;
}
#header, #footer
{
  width: 100%;
  color: #fff;
  height: 40px;
  position: absolute;
  text-align: center;
  line-height: 40px;
  overflow: hidden;
  font-size: 12px;
  vertical-align: middle;
}
#header
{
  background: #000;
  top: 0;
  font-size: 14px;
}
#footer
{
  bottom: 0;
  background: #000 url(footerbg.png) repeat-x 0 top;
  position: relative;
  margin-top: -40px;
  clear: both;
}
.header, .footer
{
  width: 750px;
  margin-right: auto;
  margin-left: auto;
}
.wrapper
{
  width: 100%;
}
#top, #top-small, #bottom
{
  width: 100%;
}
#top
{
  color: #000;
  height: 230px;
  background: #fff url(headerbg.png) repeat-x 0 top;
  position: relative;
}
#top-small
{
  color: #000;
  height: 60px;
  background: #fff url(headerbg.png) repeat-x 0 top;
  position: relative;
}
#bottom
{
  color: #222;
  background-color: #fff;
}
.top, .top-small, .middle, .bottom
{
  width: 750px;
  margin-right: auto;
  margin-left: auto;
}
.top
{
  padding-top: 40px;
}
.top-small
{
  padding-top: 10px;
}
#middle
{
  width: 100%;
  height: 100px;
  background: url(middlebg.png) repeat-x;
  border-top: 2px solid #fff;
  border-bottom: 2px solid #b2b2b2;
}
.app-welcome
{
  margin-top: 25px;
}
.app-name
{
  color: #000;
  font-weight: 700;
}
.bottom
{
  padding-top: 50px;
}
#left
{
  width: 350px;
  float: left;
  padding-right: 25px;
}
#right
{
  width: 350px;
  float: right;
  padding-left: 25px;
}
.align-left
{
  text-align: left;
}
.align-right
{
  text-align: right;
}
.align-center
{
  text-align: center;
}
ul.links
{
  margin: 0;
  padding: 0;
}
ul.links li
{
  list-style-type: none;
  font-size: 14px;
}
form
{
  border-style: none;
}
fieldset
{
  border-style: none;
}
input
{
  color: #222;
  border: 1px solid #ccc;
  font-family: sans-serif;
  font-size: 12px;
  line-height: 16px;
}
input[type=text], input[type=password]
{
  width: 205px;
}
input[type=submit]
{
  background-color: #ddd;
  font-weight: 700;
}
/*Opera Fix*/
body:before
{
  content: "";
  height: 100%;
  float: left;
  width: 0;
  margin-top: -32767px;
}
docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png
docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png
docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif
docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt
New file
@@ -0,0 +1,76 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
  <title>The Pyramid Web Application Development Framework</title>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
  <meta name="keywords" content="python web application" />
  <meta name="description" content="pyramid web application" />
  <link rel="shortcut icon" href="${request.static_url('scaffolds:static/favicon.ico')}" />
  <link rel="stylesheet" href="${request.static_url('scaffolds:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" />
  <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" />
  <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" />
  <!--[if lte IE 6]>
  <link rel="stylesheet" href="${request.static_url('scaffolds:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" />
  <![endif]-->
</head>
<body>
  <div id="wrap">
    <div id="top">
      <div class="top align-center">
          <div><img src="${request.static_url('scaffolds:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div>
      </div>
    </div>
    <div id="middle">
      <div class="middle align-center">
        <p class="app-welcome">
          Welcome to <span class="app-name">${project}</span>, an application generated by<br/>
          the Pyramid web application development framework.
        </p>
      </div>
    </div>
    <div id="bottom">
      <div class="bottom">
        <div id="left" class="align-right">
          <h2>Search documentation</h2>
          <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html">
                <input type="text" id="q" name="q" value="" />
                <input type="submit" id="x" value="Go" />
            </form>
        </div>
        <div id="right" class="align-left">
          <h2>Pyramid links</h2>
          <ul class="links">
            <li>
              <a href="http://pylonsproject.org">Pylons Website</a>
            </li>
            <li>
              <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a>
            </li>
            <li>
              <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a>
            </li>
            <li>
              <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a>
            </li>
            <li>
              <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a>
            </li>
            <li>
              <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a>
            </li>
            <li>
              <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a>
            </li>
            <li>
              <a href="irc://irc.freenode.net#pyramid">IRC Channel</a>
            </li>
            </ul>
        </div>
      </div>
    </div>
  </div>
  <div id="footer">
    <div class="footer">&copy; Copyright 2008-2012, Agendaless Consulting.</div>
  </div>
</body>
</html>
docs/quick_tutorial/scaffolds/scaffolds/tests.py
New file
@@ -0,0 +1,17 @@
import unittest
from pyramid import testing
class ViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_my_view(self):
        from .views import my_view
        request = testing.DummyRequest()
        info = my_view(request)
        self.assertEqual(info['project'], 'scaffolds')
docs/quick_tutorial/scaffolds/scaffolds/views.py
New file
@@ -0,0 +1,6 @@
from pyramid.view import view_config
@view_config(route_name='home', renderer='templates/mytemplate.pt')
def my_view(request):
    return {'project': 'scaffolds'}
docs/quick_tutorial/scaffolds/setup.cfg
New file
@@ -0,0 +1,27 @@
[nosetests]
match = ^test
nocapture = 1
cover-package = scaffolds
with-coverage = 1
cover-erase = 1
[compile_catalog]
directory = scaffolds/locale
domain = scaffolds
statistics = true
[extract_messages]
add_comments = TRANSLATORS:
output_file = scaffolds/locale/scaffolds.pot
width = 80
[init_catalog]
domain = scaffolds
input_file = scaffolds/locale/scaffolds.pot
output_dir = scaffolds/locale
[update_catalog]
domain = scaffolds
input_file = scaffolds/locale/scaffolds.pot
output_dir = scaffolds/locale
previous = true
docs/quick_tutorial/scaffolds/setup.py
New file
@@ -0,0 +1,39 @@
import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
README = open(os.path.join(here, 'README.txt')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
requires = [
    'pyramid',
    'pyramid_debugtoolbar',
    'waitress',
    ]
setup(name='scaffolds',
      version='0.0',
      description='scaffolds',
      long_description=README + '\n\n' + CHANGES,
      classifiers=[
        "Programming Language :: Python",
        "Framework :: Pyramid",
        "Topic :: Internet :: WWW/HTTP",
        "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
        ],
      author='',
      author_email='',
      url='',
      keywords='web pyramid pylons',
      packages=find_packages(),
      include_package_data=True,
      zip_safe=False,
      install_requires=requires,
      tests_require=requires,
      test_suite="scaffolds",
      entry_points="""\
      [paste.app_factory]
      main = scaffolds:main
      """,
      )
docs/quick_tutorial/sessions.rst
New file
@@ -0,0 +1,98 @@
=================================
17: Transient Data Using Sessions
=================================
Store and retrieve non-permanent data in Pyramid sessions.
Background
==========
When people use your web application, they frequently perform a task
that requires semi-permanent data to be saved. For example, a shopping
cart. This is called a :term:`session`.
Pyramid has basic built-in support for sessions, with add-ons such as
*dogpile.cache* (or your own custom sessioning engine) that provide
richer session support. Let's take a look at the
:ref:`built-in sessioning support <pyramid:sessions_chapter>`.
Objectives
==========
- Make a session factory using a built-in, simple Pyramid sessioning
  system
- Change our code to use a session
Steps
=====
#. First we copy the results of the ``view_classes`` step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes sessions; cd sessions
    (env27)$ python setup.py develop
#. Our ``sessions/tutorial/__init__.py`` needs a choice of session
   factory to get registered with the :term:`configurator`:
   .. literalinclude:: sessions/tutorial/__init__.py
    :linenos:
#. Our views in ``sessions/tutorial/views.py`` can now use
   ``request.session``:
   .. literalinclude:: sessions/tutorial/views.py
    :linenos:
#. The template at ``sessions/tutorial/home.pt`` can display the value:
   .. literalinclude:: sessions/tutorial/home.pt
    :language: html
    :linenos:
#. Make sure the tests still pass:
   .. code-block:: bash
    (env27)$ nosetests tutorial
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` and ``http://localhost:6543/howdy``
   in your browser. As you reload and switch between those URLs, note
   that the counter increases and is *not* specific to the URL.
#. Restart the application and revisit the page. Note that counter
   still increases from where it left off.
Analysis
========
Pyramid's :term:`request` object now has a ``session`` attribute
that we can use in our view code. It acts like a dictionary.
Since all the views are using the same counter, we made the counter a
Python property at the view class level. With this, each reload will
increase the counter displayed in our template.
In web development, "flash messages" are notes for the user that need
to appear on a screen after a future web request. For example,
when you add an item using a form ``POST``, the site usually issues a
second HTTP Redirect web request to view the new item. You might want a
message to appear after that second web request saying "Your item was
added." You can't just return it in the web response for the POST,
as it will be tossed out during the second web requests.
Flash messages are a technique where messages can be stored between
requests, using sessions, then removed when they finally get displayed.
.. seealso::
   :ref:`pyramid:sessions_chapter`,
   :ref:`pyramid:flash_messages`, and
   :ref:`pyramid:session_module`.
docs/quick_tutorial/sessions/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/sessions/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/sessions/tutorial/__init__.py
New file
@@ -0,0 +1,13 @@
from pyramid.config import Configurator
from pyramid.session import UnencryptedCookieSessionFactoryConfig
def main(global_config, **settings):
    my_session_factory = UnencryptedCookieSessionFactoryConfig(
        'itsaseekreet')
    config = Configurator(settings=settings,
                          session_factory=my_session_factory)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/sessions/tutorial/home.pt
New file
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Hi ${name}</h1>
<p>Count: ${view.counter}</p>
</body>
</html>
docs/quick_tutorial/sessions/tutorial/tests.py
New file
@@ -0,0 +1,44 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/sessions/tutorial/views.py
New file
@@ -0,0 +1,29 @@
from pyramid.view import (
    view_config,
    view_defaults
    )
@view_defaults(renderer='home.pt')
class TutorialViews:
    def __init__(self, request):
        self.request = request
    @property
    def counter(self):
        session = self.request.session
        if 'counter' in session:
            session['counter'] += 1
        else:
            session['counter'] = 1
        return session['counter']
    @view_config(route_name='home')
    def home(self):
        return {'name': 'Home View'}
    @view_config(route_name='hello')
    def hello(self):
        return {'name': 'Hello View'}
docs/quick_tutorial/static_assets.rst
New file
@@ -0,0 +1,89 @@
==========================================
13: CSS/JS/Images Files With Static Assets
==========================================
Of course the Web is more than just markup. You need static assets:
CSS, JS, and images. Let's point our web app at a directory where
Pyramid will serve some static assets.
Objectives
==========
- Publish a directory of static assets at a URL
- Use Pyramid to help generate URLs to files in that directory
Steps
=====
#. First we copy the results of the ``view_classes`` step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes static_assets; cd static_assets
    (env27)$ python setup.py develop
#. We add a call ``config.add_static_view in
   ``static_assets/tutorial/__init__.py``:
   .. literalinclude:: static_assets/tutorial/__init__.py
    :linenos:
#. We can add a CSS link in the ``<head>`` of our template at
   ``static_assets/tutorial/home.pt``:
   .. literalinclude:: static_assets/tutorial/home.pt
    :language: html
#. Add a CSS file at
   ``static_assets/tutorial/static/app.css``:
   .. literalinclude:: static_assets/tutorial/static/app.css
    :language: css
#. Make sure we haven't broken any existing code by running the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
We changed our WSGI application to map requests under
``http://localhost:6543/static/`` to files and directories inside a
``static`` directory inside our ``tutorial`` package. This directory
contained ``app.css``.
We linked to the CSS in our template. We could have hard-coded this
link to ``/static/app.css``. But what if the site is later moved under
``/somesite/static/``? Or perhaps the web developer changes the
arrangement on disk? Pyramid gives a helper that provides flexibility
on URL generation:
.. code-block:: html
  ${request.static_url('tutorial:static/app.css')}
This matches the ``path='tutorial:static'`` in our
``config.add_static_view`` registration. By using ``request.static_url``
to generate the full URL to the static assets, you both ensure you stay
in sync with the configuration and gain refactoring flexibility later.
Extra Credit
============
#. There is also a ``request.static_path`` API.  How does this differ from
   ``request.static_url``?
.. seealso:: :ref:`pyramid:assets_chapter`,
   :ref:`pyramid:preventing_http_caching`, and
   :ref:`pyramid:influencing_http_caching`
docs/quick_tutorial/static_assets/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/static_assets/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/static_assets/tutorial/__init__.py
New file
@@ -0,0 +1,10 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.add_static_view(name='static', path='tutorial:static')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/static_assets/tutorial/home.pt
New file
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
    <link rel="stylesheet"
          href="${request.static_url('tutorial:static/app.css') }"/>
</head>
<body>
<h1>Hi ${name}</h1>
</body>
</html>
docs/quick_tutorial/static_assets/tutorial/static/app.css
New file
@@ -0,0 +1,4 @@
body {
    margin: 2em;
    font-family: sans-serif;
}
docs/quick_tutorial/static_assets/tutorial/tests.py
New file
@@ -0,0 +1,44 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/static_assets/tutorial/views.py
New file
@@ -0,0 +1,18 @@
from pyramid.view import (
    view_config,
    view_defaults
    )
@view_defaults(renderer='home.pt')
class TutorialViews:
    def __init__(self, request):
        self.request = request
    @view_config(route_name='home')
    def home(self):
        return {'name': 'Home View'}
    @view_config(route_name='hello')
    def hello(self):
        return {'name': 'Hello View'}
docs/quick_tutorial/templating.rst
New file
@@ -0,0 +1,100 @@
===================================
08: HTML Generation With Templating
===================================
Most web frameworks don't embed HTML in programming code. Instead,
they pass data into a templating system. In this step we look at the
basics of using HTML templates in Pyramid.
Background
==========
Ouch. We have been making our own ``Response`` and filling the response
body with HTML. You usually won't embed an HTML string directly in
Python, but instead, will use a templating language.
Pyramid doesn't mandate a particular database system, form library,
etc. It encourages replaceability. This applies equally to templating,
which is fortunate: developers have strong views about template
languages. That said, Pyramid bundles Chameleon and Mako,
so in this step, let's use Chameleon as an example.
Objectives
==========
- Generate HTML from template files
- Connect the templates as "renderers" for view code
- Change the view code to simply return data
Steps
=====
#. Let's begin by using the previous package as a starting point for a new
   distribution, then making it active:
   .. code-block:: bash
    (env27)$ cd ..; cp -r views templating; cd templating
    (env27)$ python setup.py develop
#. Our ``templating/tutorial/views.py`` no longer has HTML in it:
   .. literalinclude:: templating/tutorial/views.py
    :linenos:
#. Instead we have ``templating/tutorial/home.pt`` as a template:
   .. literalinclude:: templating/tutorial/home.pt
    :language: html
#. For convenience, change ``templating/development.ini`` to reload
   templates automatically with ``pyramid.reload_templates``:
   .. literalinclude:: templating/development.ini
    :language: ini
#. Our unit tests in ``templating/tutorial/tests.py`` can focus on
   data:
   .. literalinclude:: templating/tutorial/tests.py
    :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
    .
    ----------------------------------------------------------------------
    Ran 4 tests in 0.141s
    OK
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` and ``http://localhost:6543/howdy``
   in your browser.
Analysis
========
Ahh, that looks better. We have a view that is focused on Python code.
Our ``@view_config`` decorator specifies a
:term:`pyramid:renderer` that points
our template file. Our view then simply returns data which is then
supplied to our template. Note that we used the same template for both
views.
Note the effect on testing. We can focus on having a data-oriented
contract with our view code.
.. seealso:: :ref:`pyramid:templates_chapter`,
   :ref:`pyramid:debugging_templates`, and
   :ref:`pyramid:mako_templates`
docs/quick_tutorial/templating/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/templating/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/templating/tutorial/__init__.py
New file
@@ -0,0 +1,9 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/templating/tutorial/home.pt
New file
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Hi ${name}</h1>
</body>
</html>
docs/quick_tutorial/templating/tutorial/tests.py
New file
@@ -0,0 +1,44 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import home
        request = testing.DummyRequest()
        response = home(request)
        # Our view now returns data
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import hello
        request = testing.DummyRequest()
        response = hello(request)
        # Our view now returns data
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/templating/tutorial/views.py
New file
@@ -0,0 +1,13 @@
from pyramid.view import view_config
# First view, available at http://localhost:6543/
@view_config(route_name='home', renderer='home.pt')
def home(request):
    return {'name': 'Home View'}
# /howdy
@view_config(route_name='hello', renderer='home.pt')
def hello(request):
    return {'name': 'Hello View'}
docs/quick_tutorial/traversal_addcontent.rst
New file
@@ -0,0 +1,105 @@
===================================
25: Adding Resources To Hierarchies
===================================
Multiple views per type allowing addition of content anywhere in a
resource tree.
Background
==========
We now have multiple kinds-of-things, but only one view per resource
type. We need the ability to add things to containers,
then view/edit resources.
This introduces the concept of named views. A name is a part of the URL
that appears after the resource identifier. For example::
  @view_config(context=Folder, name='add_document')
...means that this URL::
  http://localhost:6543/some_folder/add_document
...will match the view being configured. It's as if you have an
object-oriented web, with operations on resources represented by a URL.
When you omit the ``name=`` (as we did in the previous examples,
you are establishing a "default view" for the context. That is,
a view to be used when no view name is found during traversal.
Goals
=====
- Adding and editing content in a resource tree
- Simple form which POSTs data
- A view which takes the POST data, creates a resource, and redirects
  to the newly-added resource
- Named views
Steps
=====
#. We are going to use the previous step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r traversal_typeviews traversal_addcontent; cd traversal_addcontent
    (env27)$ python setup.py develop
#. Our views in ``traversal_addcontent/tutorial/views.py`` need
   type-specific registrations:
   .. literalinclude:: traversal_addcontent/tutorial/views.py
      :linenos:
#. One small change in
   ``traversal_addcontent/tutorial/templates/document.pt``:
   .. literalinclude:: traversal_addcontent/tutorial/templates/document.pt
      :language: html
      :linenos:
#. Need forms added to
   ``traversal_addcontent/tutorial/templates/folder.pt``:
   .. literalinclude:: traversal_addcontent/tutorial/templates/folder.pt
      :language: html
      :linenos:
#. Forms also needed for
   ``traversal_addcontent/tutorial/templates/site.pt``:
   .. literalinclude:: traversal_addcontent/tutorial/templates/site.pt
      :language: html
      :linenos:
#. ``$ nosetests`` should report running 4 tests.
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
Our views now represent a richer system, where form data can be
processed to modify content in the tree. We do this by attaching named
views to resource types, giving them a natural system for
object-oriented operations.
To enforce uniqueness, we randomly choose a satisfactorily large number.
Extra Credit
============
#. Can ``document_view`` simply return nothing instead of an empty
   dictionary?
docs/quick_tutorial/traversal_hierarchy.rst
New file
@@ -0,0 +1,106 @@
=========================
23: Traversal Hierarchies
=========================
Objects with subobjects and views, all via URLs.
Background
==========
In :doc:`../traversal_siteroot` we took the simplest possible step: a
root object with little need for the stitching-together of a tree known
as traversal.
In this step we remain simple, but make a basic hierarchy::
    /
       doc1
       doc2
       folder1/
          doc1
Objectives
==========
- Multi-level nested hierarchy of Python objects
- Show how ``__name__`` and ``__parent__`` glue the hierarchy together
- Objects which last between requests
Steps
=====
#. We are going to use the previous step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r traversal_siteroot traversal_hierarchy; cd traversal_hierarchy
    (env27)$ python setup.py develop
#. Provide a richer set of objects in
   ``traversal_hierarchy/tutorial/resources.py``:
   .. literalinclude:: traversal_hierarchy/tutorial/resources.py
      :linenos:
#. Have ``traversal_hierarchy/tutorial/views.py`` show information about
   the resource tree:
   .. literalinclude:: traversal_hierarchy/tutorial/views.py
      :linenos:
#. Get ``traversal_hierarchy/tutorial/home.pt`` to display this richer
   information:
   .. literalinclude:: traversal_hierarchy/tutorial/home.pt
      :language: html
      :linenos:
#. Simplified tests in ``traversal_hierarchy/tutorial/tests.py``:
   .. literalinclude:: traversal_hierarchy/tutorial/tests.py
      :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
    .
    ----------------------------------------------------------------------
    Ran 4 tests in 0.141s
    OK
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
In this example we have to manage our tree by assigning ``__name__`` as
an identifier on each child and ``__parent__`` as a reference to the
parent.
The template used now shows different information based on the object
URL which you traversed to.
Extra Credit
============
#. In ``resources.py``, we moved the instantiation of ``root`` out to
   global scope. Why?
#. If you go to a resource that doesn't exist, will Pyramid handle it
   gracefully?
#. What happens if you use a ``__name__`` that already exists in the
   container?
docs/quick_tutorial/traversal_siteroot.rst
New file
@@ -0,0 +1,153 @@
===================================
22: Basic Traversal With Site Roots
===================================
Model websites as a hierarchy of objects with operations.
Background
==========
Web applications have URLs which locate data and make operations on that
data. Pyramid supports two ways of mapping URLs into Python operations:
- The more-traditional approach of *URL dispatch* aka *routes*
- The more object-oriented approach of
  :ref:`traversal <pyramid:traversal_chapter>` popularized by Zope
In this section we will introduce traversal bit-by-bit. Along the way,
we will try to show how easy and Pythonic it is to think in terms of
traversal.
Remember...traversal is easy, powerful, and useful.
With traversal, you think of your website as a tree of Python objects,
just like a dictionary of dictionaries. For example::
  http://example.com/company1/aFolder/subFolder/search?term=hello
...is nothing more than::
  >>> root['aFolder']['subFolder'].search(x=1)
To remove some mystery about traversal, we start with the smallest
possible step: an object at the top of our URL space. This object acts
as the "root" and has a view which shows some data on that object.
Objectives
==========
- Make a factory for the root object
- Pass it to the configurator
- Have a view which displays an attribute on that object
Steps
=====
#. We are going to use the view classes step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r view_classes traversal_siteroot; cd traversal_siteroot
    (env27)$ python setup.py develop
#. In ``traversal_siteroot/tutorial/__init__.py`` make a root factory
   and remove the ``add_route`` statements from the
   :term:`configurator`:
   .. literalinclude:: traversal_siteroot/tutorial/__init__.py
      :linenos:
#. We have ``traversal_siteroot/tutorial/resources.py`` with a class for
   the root of our site and a factory that returns it:
   .. literalinclude:: traversal_siteroot/tutorial/resources.py
      :linenos:
#. Our views in ``traversal_siteroot/tutorial/views.py`` are now
   quite different...no ``route_name``:
   .. literalinclude:: traversal_siteroot/tutorial/views.py
      :linenos:
#. A template in ``traversal_siteroot/tutorial/home.pt``:
   .. literalinclude:: traversal_siteroot/tutorial/home.pt
    :language: html
    :linenos:
#. Simplified tests in ``traversal_siteroot/tutorial/tests.py``:
   .. literalinclude:: traversal_siteroot/tutorial/tests.py
      :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
    .
    ----------------------------------------------------------------------
    Ran 4 tests in 0.141s
    OK
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
Our ``__init__.py`` has a small but important change: we create the
configuration with a *root factory*. Our root factory is a simple
function that performs some work and returns the root object in the
:ref:`resource tree <pyramid:the_resource_tree>`.
In the resource tree, Pyramid can match URLs to objects and subobjects,
finishing in a view as the operation to perform. Traversing through
containers is done using Python's normal ``__getitem__`` dictionary
protocol.
Pyramid provides services beyond simple Python dictionaries. These
:ref:`location <pyramid:location_aware>`
services need a little bit more protocol than just ``__getitem__``.
Namely, objects need to provide an attribute/callable for
``__name__`` and ``__parent__``.
In this step, our tree has one object: the root. It is an instance of
``SiteFolder``. Since it is the root, it doesn't need a ``__name__``
(aka ``id``) nor a ``__parent__`` (reference to the container an object
is in.)
Our ``home`` view is passed, by Pyramid, the instance of this folder as
``context``. The view can then grab attributes and other data from the
object that is the focus of the URL.
Now, on to the most visible part: no more routes! Previously we wrote
URL "replacement patterns" which mapped to a route. The route extracted
data from the patterns and made this data available to views that were
mapped to that route.
Instead, segments in URLs become object identifiers in Python.
Extra Credit
============
#. Is the root factory called once on startup, or on every request? Do
   a small change that answers this. What is the impact of the answer
   on this?
.. seealso::
   :ref:`pyramid:traversal_chapter`,
   :ref:`pyramid:location_aware`,
   :ref:`pyramid:the_resource_tree`,
   :ref:`much_ado_about_traversal_chapter`
docs/quick_tutorial/traversal_typeviews.rst
New file
@@ -0,0 +1,112 @@
=======================
24: Type-Specific Views
=======================
Type-specific views by registering a view against a class.
Background
==========
In :doc:`../traversal_hierarchy` we had 3 "content types" (SiteFolder,
Folder, and Document.) All, however, used the same view and template.
Pyramid traversal though lets you bind a view to a particular content
type. This ability to make your URLs "object oriented" is one of the
distinguishing features of traversal and makes crafting a URL space
more natural. Once Pyramid finds the :term:`context` object in the URL
path, developers have a lot of flexibility in view predicates.
Objectives
==========
- ``@view_config`` which uses the ``context`` attribute to associate a
  particular view with ``context`` instances of a particular class
- Views and templates which are unique to a particular class (aka type)
- Patterns in test writing to handle multiple kinds of contexts
Steps
=====
#. We are going to use the previous step as our starting point and add a
   ``tutorial/templates`` subdirectory:
   .. code-block:: bash
    (env27)$ cd ..; cp -r traversal_hierarchy traversal_typeviews; cd traversal_typeviews
    (env27)$ python setup.py develop
    (env27)$ mkdir traversal_typeviews/tutorial/templates
#. Our views in ``traversal_typeviews/tutorial/views.py`` need
   type-specific registrations:
   .. literalinclude:: traversal_typeviews/tutorial/views.py
      :linenos:
#. Copy the following into
   ``traversal_typeviews/tutorial/templates/document.pt``:
   .. literalinclude:: traversal_typeviews/tutorial/templates/document.pt
      :language: html
      :linenos:
#. Copy the following into
   ``traversal_typeviews/tutorial/templates/folder.pt``:
   .. literalinclude:: traversal_typeviews/tutorial/templates/folder.pt
      :language: html
      :linenos:
#. Copy the following into
   ``traversal_typeviews/tutorial/templates/site.pt``:
   .. literalinclude:: traversal_typeviews/tutorial/templates/site.pt
      :language: html
      :linenos:
#. More tests needed in ``traversal_typeviews/tutorial/tests.py``:
   .. literalinclude:: traversal_typeviews/tutorial/tests.py
      :linenos:
#. ``$ nosetests`` should report running 4 tests.
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
We made a ``templates`` subdirectory, just for the purposes of
organization and to match a common project layout style.
For the most significant change, our ``@view_config`` now matches on a
``context`` view predicate. We can say "use this view for when looking
at *this* kind of thing." The concept of a route as an intermediary
step between URLs and views has been eliminated.
Extra Credit
============
#. Should you calculate the list of children on the Python side,
   or access it on the template side by operating on the context?
#. What if you need different traversal policies?
#. In Zope, *interfaces* were used to register a view. How do you do
   register a Pyramid view against instances that support a particular
   interface? When should you?
#. Let's say you need a more-specific view to be used on a particular
   instance of a class, letting a more-general view cover all other
   instances. What are some of your options?
.. seealso::
   :ref:`Traversal Details <pyramid:traversal_chapter>`
   :ref:`Hybrid Traversal and URL Dispatch <pyramid:hybrid_chapter>`
docs/quick_tutorial/traversal_zodb.rst
New file
@@ -0,0 +1,121 @@
==================================
26: Storing Resources In Databases
==================================
Store and retrieve resource tree containers and items in a database.
Background
==========
We now have a resource tree that can go infinitely deep,
adding items and subcontainers along the way. We obviously need a
database, one that can support hierarchies. ZODB is a transaction-based
Python database that supports transparent persistence. We will modify
our application to work with the ZODB.
Along the way we will add the use of ``pyramid_tm``,
a system for adding transaction awareness to our code. With this we
don't need to manually manage our transaction begin/commit cycles in
our application code. Instead, transactions are setup transparently on
request/response boundaries, outside our application code.
Objectives
==========
- Create a CRUD app that adds records to persistent storage.
- Setup ``pyramid_tm`` and ``pyramid_zodbconn``.
- Make our "content" classes inherit from ``Persistent``.
- Set up a database connection string in our application.
- Set up a root factory that serves the root from ZODB rather than from
  memory.
Steps
=====
#. We are going to use the previous step as our starting point:
   .. code-block:: bash
    (env27)$ cd ..; cp -r traversal_addcontent traversal_zodb; cd traversal_zodb
#. Introduce some new dependencies in  ``traversal_zodb/setup.py``:
   .. literalinclude:: traversal_zodb/setup.py
      :linenos:
#. We can now install our project:
   .. code-block:: bash
    (env27)$ python setup.py develop
#. Modify our ``traversal_zodb/development.ini`` to include some
   configuration and give database connection parameters:
   .. literalinclude:: traversal_zodb/development.ini
      :language: ini
      :linenos:
#. Our startup code in ``traversal_zodb/tutorial/__init__.py`` gets
   some bootstrapping changes:
   .. literalinclude:: traversal_zodb/tutorial/__init__.py
      :linenos:
#. Our views in ``traversal_zodb/tutorial/views.py`` change to create
   persistent objects:
   .. literalinclude:: traversal_zodb/tutorial/views.py
      :linenos:
#. As do our resources in ``traversal_zodb/tutorial/resources.py``:
   .. literalinclude:: traversal_zodb/tutorial/resources.py
      :linenos:
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` in your browser.
Analysis
========
We install ``pyramid_zodbconn`` to handle database connections to ZODB. This
pulls the ZODB3 package as well.
To enable ``pyramid_zodbconn``:
- We activate the package configuration using ``pyramid.includes``.
- We define a ``zodbconn.uri`` setting with the path to the Data.fs file.
In the root factory, instead of using our old root object, we now get a
connection to the ZODB and create the object using that.
Our resources need a couple of small changes. Folders now inherit from
persistent.PersistentMapping and document from persistent.Persistent. Note
that Folder now needs to call super() on the __init__ method, or the
mapping will not initialize properly.
On the bootstrap, note the use of transaction.commit() to commit the
change. This is because, on first startup, we want a root resource in
place before continuing.
Extra Credit
============
#. Create a view that deletes a document.
#. Remove the configuration line that includes ``pyramid_tm``.  What
   happens when you restart the application?  Are your changes
   persisted across restarts?
#. What happens if you delete the files named ``Data.fs*``?
docs/quick_tutorial/tutorial_approach.rst
New file
@@ -0,0 +1,45 @@
=================
Tutorial Approach
=================
In summary:
- Tutorial broken into topics with quick working examples
- Each step is a Python *package* with working code in the repo
- Setup each step with ``python setup.py develop``
This "Getting Started" tutorial is broken into independent steps,
starting with the smallest possible "single file WSGI app" example.
Each of these steps introduce a topic and a very small set of concepts
via working code. The steps each correspond to a directory in this
repo, where each step/topic/directory is a Python package.
To successfully run each step::
  $ cd request_response
  $ python setup.py develop
...and repeat for each step you would like to work on. In most cases we
will start with the results of an earlier step.
Directory Tree
==============
As we develop our tutorial our directory tree will resemble the
structure below::
  request_response/
    development.ini
    setup.py
    tutorial/
      __init__.py
      home.pt
      tests.py
      views.py
Each of the first-level directories are a *Python project*
(except, as noted, the first.) The ``tutorial`` directory is a *Python
package*. At the end of each step, we copy the old directory into a new
directory to use as a starting point.
docs/quick_tutorial/unit_testing.rst
New file
@@ -0,0 +1,117 @@
===========================
05: Unit Tests and ``nose``
===========================
Provide unit testing for our project's Python code.
Background
==========
As the mantra says, "Untested code is broken code." The Python
community has had a long culture of writing test scripts which ensure
that your code works correctly as you write it and maintain it in the
future. Pyramid has always had a deep commitment to testing,
with 100% test coverage from the earliest pre-releases.
Python includes a
:ref:`unit testing framework <python:unittest-minimal-example>` in its
standard library. Over the years a number of Python projects, such as
`nose <https://pypi.python.org/pypi/nose/>`_, have extended this
framework with alternative test runners that provide more convenience
and functionality. The Pyramid developers use ``nose``, which we'll thus
use in this tutorial.
Don't worry, this tutorial won't be pedantic about "test-driven
development" (TDD.) We'll do just enough to ensure that, in each step,
we haven't majorly broken the code. As you're writing your code you
might find this more convenient than changing to your browser
constantly and clicking reload.
We'll also leave discussion of
`coverage <https://pypi.python.org/pypi/coverage>`_ for another section.
Objectives
==========
- Write unit tests that ensure the quality of our code
- Install a Python package (``nose``) which helps in our testing
Steps
=====
#. First we copy the results of the previous step, as well as install
   the ``nose`` package:
   .. code-block:: bash
    (env27)$ cd ..; cp -r debugtoolbar unit_testing; cd unit_testing
    (env27)$ python setup.py develop
    (env27)$ easy_install nose
#. Now we write a simple unit test in ``unit_testing/tutorial/tests.py``:
   .. literalinclude:: unit_testing/tutorial/tests.py
    :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.141s
    OK
Analysis
========
Our ``tests.py`` imports the Python standard unit testing framework. To
make writing Pyramid-oriented tests more convenient, Pyramid supplies
some ``pyramid.testing`` helpers which we use in the test setup and
teardown. Our one test imports the view, makes a dummy request, and sees
if the view returns what we expected.
The ``tests.HelloWorldViewTests.test_hello_world`` test is a small
example of a unit test. First, we import the view inside each test. Why
not import at the top, like in normal Python code? Because imports can
cause effects that break a test. We'd like our tests to be in *units*,
hence the name *unit* testing. Each test should isolate itself to the
correct degree.
Our test then makes a fake incoming web request, then calls our Pyramid
view. We test the HTTP status code on the response to make sure it
matches our expectations.
Note that our use of ``pyramid.testing.setUp()`` and
``pyramid.testing.tearDown()`` aren't actually necessary here; they are only
necessary when your test needs to make use of the ``config`` object (it's a
Configurator) to add stuff to the configuration state before calling the view.
Extra Credit
============
#. Change the test to assert that the response status code should be
   ``404`` (meaning, not found.) Run ``nosetests`` again. Read the
   error report and see if you can decipher what it is telling you.
#. As a more realistic example, put the ``tests.py`` back as you found
   it and put an error in your view, such as a reference to a
   non-existing variable. Run the tests and see how this is more
   convenient than reloading your browser and going back to your code.
#. Finally, for the most realistic test, read about Pyramid ``Response``
   objects and see how to change the response code. Run the tests and
   see how testing confirms the "contract" that your code claims to
   support.
#. How could we add a unit test assertion to test the HTML value of the
   response body?
#. Why do we import the ``hello_world`` view function *inside* the
   ``test_hello_world`` method instead of at the top of the module?
.. seealso:: See Also: :ref:`pyramid:testing_chapter`
docs/quick_tutorial/unit_testing/development.ini
New file
@@ -0,0 +1,40 @@
[app:main]
use = egg:tutorial
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/unit_testing/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/unit_testing/tutorial/__init__.py
New file
@@ -0,0 +1,13 @@
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
    return Response('<body><h1>Hello World!</h1></body>')
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('hello', '/')
    config.add_view(hello_world, route_name='hello')
    return config.make_wsgi_app()
docs/quick_tutorial/unit_testing/tutorial/tests.py
New file
@@ -0,0 +1,18 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_hello_world(self):
        from tutorial import hello_world
        request = testing.DummyRequest()
        response = hello_world(request)
        self.assertEqual(response.status_code, 200)
docs/quick_tutorial/view_classes.rst
New file
@@ -0,0 +1,96 @@
======================================
09: Organizing Views With View Classes
======================================
Change our view functions to be methods on a view class,
then move some declarations to the class level.
Background
==========
So far our views have been simple, free-standing functions. Many times
your views are related: different ways to look at or work on the same
data or a REST API that handles multiple operations. Grouping these
together as a
:ref:`view class <pyramid:class_as_view>` makes sense:
- Group views
- Centralize some repetitive defaults
- Share some state and helpers
In this step we just do the absolute minimum to convert the existing
views to a view class. In a later tutorial step we'll examine view
classes in depth.
Objectives
==========
- Group related views into a view class
- Centralize configuration with class-level ``@view_defaults``
Steps
=====
#. First we copy the results of the previous step:
   .. code-block:: bash
    (env27)$ cd ..; cp -r templating view_classes; cd view_classes
    (env27)$ python setup.py develop
#. Our ``view_classes/tutorial/views.py`` now has a view class with
   our two views:
   .. literalinclude:: view_classes/tutorial/views.py
    :linenos:
#. Our unit tests in ``view_classes/tutorial/tests.py`` don't run,
   so let's modify the to import the view class and make an instance
   before getting a response:
   .. literalinclude:: view_classes/tutorial/tests.py
    :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
    .
    ----------------------------------------------------------------------
    Ran 4 tests in 0.141s
    OK
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` and ``http://localhost:6543/howdy``
   in your browser.
Analysis
========
To ease the transition to view classes, we didn't introduce any new
functionality. We simply changed the view functions to methods on a
view class, then updated the tests.
In our ``TutorialViews`` view class you can see that our two view
classes are logically grouped together as methods on a common class.
Since the two views shared the same template, we could move that to a
``@view_defaults`` decorator on at the class level.
The tests needed to change. Obviously we needed to import the view
class. But you can also see the pattern in the tests of instantiating
the view class with the dummy request first, then calling the view
method being tested.
.. seealso:: :ref:`pyramid:class_as_view`
docs/quick_tutorial/view_classes/development.ini
New file
@@ -0,0 +1,41 @@
[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/view_classes/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/view_classes/tutorial/__init__.py
New file
@@ -0,0 +1,9 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/view_classes/tutorial/home.pt
New file
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Quick Tour: ${name}</title>
</head>
<body>
<h1>Hi ${name}</h1>
</body>
</html>
docs/quick_tutorial/view_classes/tutorial/tests.py
New file
@@ -0,0 +1,44 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.home()
        self.assertEqual('Home View', response['name'])
    def test_hello(self):
        from .views import TutorialViews
        request = testing.DummyRequest()
        inst = TutorialViews(request)
        response = inst.hello()
        self.assertEqual('Hello View', response['name'])
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<h1>Hi Home View', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<h1>Hi Hello View', res.body)
docs/quick_tutorial/view_classes/tutorial/views.py
New file
@@ -0,0 +1,17 @@
from pyramid.view import (
    view_config,
    view_defaults
    )
@view_defaults(renderer='home.pt')
class TutorialViews:
    def __init__(self, request):
        self.request = request
    @view_config(route_name='home')
    def home(self):
        return {'name': 'Home View'}
    @view_config(route_name='hello')
    def hello(self):
        return {'name': 'Hello View'}
docs/quick_tutorial/views.rst
New file
@@ -0,0 +1,120 @@
=================================
07: Basic Web Handling With Views
=================================
Organize a views module with decorators and multiple views.
Background
==========
For the examples so far, the ``hello_world`` function is a "view". In
Pyramid, views are the primary way to accept web requests and return
responses.
So far our examples place everything in one file:
- The view function
- Its registration with the configurator
- The route to map it to a URL
- The WSGI application launcher
Let's move the views out to their own ``views.py`` module and change
our startup code to scan that module, looking for decorators that setup
the views. Let's also add a second view and update our tests.
Objectives
==========
- Views in a module that is scanned by the configurator
- Decorators that do declarative configuration
Steps
=====
#. Let's begin by using the previous package as a starting point for a
   new distribution, then making it active:
   .. code-block:: bash
    (env27)$ cd ..; cp -r function_testing views; cd views
    (env27)$ python setup.py develop
#. Our ``views/tutorial/__init__.py`` gets a lot shorter:
   .. literalinclude:: views/tutorial/__init__.py
    :linenos:
#. Let's add a module ``views/tutorial/views.py`` that is focused on
   handling requests and responses:
   .. literalinclude:: views/tutorial/views.py
    :linenos:
#. Update the tests to cover the two new views:
   .. literalinclude:: views/tutorial/tests.py
    :linenos:
#. Now run the tests:
   .. code-block:: bash
    (env27)$ nosetests tutorial
    .
    ----------------------------------------------------------------------
    Ran 4 tests in 0.141s
    OK
#. Run your Pyramid application with:
   .. code-block:: bash
    (env27)$ pserve development.ini --reload
#. Open ``http://localhost:6543/`` and ``http://localhost:6543/howdy``
   in your browser.
Analysis
========
We added some more URLs, but we also removed the view code from the
application startup code in ``tutorial/__init__.py``.
Our views, and their view registrations (via decorators) are now in a
module ``views.py`` which is scanned via ``config.scan('.views')``.
We have 2 views, each leading to the other. If you start at
``http://localhost:6543/``, you get a response with a link to the next
view. The ``hello_view`` (available at the URL ``/howdy``) has a link
back to the first view.
This step also shows that the name appearing in the URL,
the name of the "route" that maps a URL to a view,
and the name of the view, can all be different. More on routes later.
Earlier we saw ``config.add_view`` as one way to configure a view. This
section introduces ``@view_config``. Pyramid's configuration supports
:term:`pyramid:imperative configuration`, such as the
``config.add_view`` in the previous example. You can also use
:term:`pyramid:declarative configuration`, in which a Python
:term:`python:decorator`
is placed on the line above the view. Both approaches result in the
same final configuration, thus usually, it is simply a matter of taste.
Extra Credit
============
#. What does the dot in ``.views`` signify?
#. Why might ``assertIn`` be a better choice in testing the text in
   responses than ``assertEqual``?
.. seealso:: :ref:`pyramid:views_chapter`,
   :ref:`pyramid:view_config_chapter`, and
   :ref:`pyramid:debugging_view_configuration`
docs/quick_tutorial/views/development.ini
New file
@@ -0,0 +1,40 @@
[app:main]
use = egg:tutorial
pyramid.includes =
    pyramid_debugtoolbar
[server:main]
use = egg:pyramid#wsgiref
host = 0.0.0.0
port = 6543
# Begin logging configuration
[loggers]
keys = root, tutorial
[logger_tutorial]
level = DEBUG
handlers =
qualname = tutorial
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
# End logging configuration
docs/quick_tutorial/views/setup.py
New file
@@ -0,0 +1,13 @@
from setuptools import setup
requires = [
    'pyramid',
]
setup(name='tutorial',
      install_requires=requires,
      entry_points="""\
      [paste.app_factory]
      main = tutorial:main
      """,
)
docs/quick_tutorial/views/tutorial/__init__.py
New file
@@ -0,0 +1,9 @@
from pyramid.config import Configurator
def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_route('home', '/')
    config.add_route('hello', '/howdy')
    config.scan('.views')
    return config.make_wsgi_app()
docs/quick_tutorial/views/tutorial/tests.py
New file
@@ -0,0 +1,44 @@
import unittest
from pyramid import testing
class TutorialViewTests(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
    def tearDown(self):
        testing.tearDown()
    def test_home(self):
        from .views import home
        request = testing.DummyRequest()
        response = home(request)
        self.assertEqual(response.status_code, 200)
        self.assertIn('Visit', response.body)
    def test_hello(self):
        from .views import hello
        request = testing.DummyRequest()
        response = hello(request)
        self.assertEqual(response.status_code, 200)
        self.assertIn('Go back', response.body)
class TutorialFunctionalTests(unittest.TestCase):
    def setUp(self):
        from tutorial import main
        app = main({})
        from webtest import TestApp
        self.testapp = TestApp(app)
    def test_home(self):
        res = self.testapp.get('/', status=200)
        self.assertIn(b'<body>Visit', res.body)
    def test_hello(self):
        res = self.testapp.get('/howdy', status=200)
        self.assertIn(b'<body>Go back', res.body)
docs/quick_tutorial/views/tutorial/views.py
New file
@@ -0,0 +1,14 @@
from pyramid.response import Response
from pyramid.view import view_config
# First view, available at http://localhost:6543/
@view_config(route_name='home')
def home(request):
    return Response('<body>Visit <a href="/howdy">hello</a></body>')
# /howdy
@view_config(route_name='hello')
def hello(request):
    return Response('<body>Go back <a href="/">home</a></body>')