summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorDariusz Górecki <darek.krk@gmail.com>2016-07-15 17:21:44 +0100
committerDariusz Górecki <darek.krk@gmail.com>2016-07-15 17:21:44 +0100
commitd54e0ae42b0289df89b5b73722d5c11d0f13a8ba (patch)
treeef4aaf5781ccdc22323476ec5407dc4112b33d7e /docs
parent744bf0565a15a40f5c04cc8b0c1fe84a2ca489da (diff)
parent37d124e64268be3a1fb82bed78c6c45eeee52140 (diff)
downloadpyramid-d54e0ae42b0289df89b5b73722d5c11d0f13a8ba.tar.gz
pyramid-d54e0ae42b0289df89b5b73722d5c11d0f13a8ba.tar.bz2
pyramid-d54e0ae42b0289df89b5b73722d5c11d0f13a8ba.zip
Merge upstream master
Diffstat (limited to 'docs')
-rw-r--r--docs/Makefile22
-rw-r--r--docs/_static/pyramid_request_processing.graffle893
-rw-r--r--docs/_static/pyramid_request_processing.pngbin122854 -> 130688 bytes
-rw-r--r--docs/_static/pyramid_request_processing.svg2
-rw-r--r--docs/api/config.rst3
-rw-r--r--docs/api/events.rst2
-rw-r--r--docs/api/exceptions.rst2
-rw-r--r--docs/api/index.rst2
-rw-r--r--docs/api/interfaces.rst12
-rw-r--r--docs/api/paster.rst2
-rw-r--r--docs/api/request.rst5
-rw-r--r--docs/api/session.rst2
-rw-r--r--docs/api/viewderivers.rst17
-rw-r--r--docs/authorintro.rst20
-rw-r--r--docs/conf.py36
-rw-r--r--docs/conventions.rst49
-rw-r--r--docs/copyright.rst11
-rw-r--r--docs/designdefense.rst1177
-rw-r--r--docs/glossary.rst227
-rw-r--r--docs/index.rst41
-rw-r--r--docs/latexindex.rst59
-rw-r--r--docs/narr/MyProject/README.txt11
-rw-r--r--docs/narr/MyProject/development.ini10
-rw-r--r--docs/narr/MyProject/myproject/static/theme.min.css1
-rw-r--r--docs/narr/MyProject/myproject/templates/mytemplate.pt13
-rw-r--r--docs/narr/MyProject/myproject/tests.py27
-rw-r--r--docs/narr/MyProject/production.ini6
-rw-r--r--docs/narr/MyProject/setup.py46
-rw-r--r--docs/narr/advconfig.rst7
-rw-r--r--docs/narr/assets.rst179
-rw-r--r--docs/narr/commandline.rst149
-rw-r--r--docs/narr/extconfig.rst2
-rw-r--r--docs/narr/extending.rst4
-rw-r--r--docs/narr/firstapp.rst8
-rw-r--r--docs/narr/hooks.rst246
-rw-r--r--docs/narr/i18n.rst70
-rw-r--r--docs/narr/install.rst411
-rw-r--r--docs/narr/introduction.rst30
-rw-r--r--docs/narr/introspector.rst25
-rw-r--r--docs/narr/logging.rst3
-rw-r--r--docs/narr/muchadoabouttraversal.rst4
-rw-r--r--docs/narr/project.rst298
-rw-r--r--docs/narr/renderers.rst2
-rw-r--r--docs/narr/router.rst25
-rw-r--r--docs/narr/scaffolding.rst8
-rw-r--r--docs/narr/security.rst8
-rw-r--r--docs/narr/sessions.rst143
-rw-r--r--docs/narr/startup.rst27
-rw-r--r--docs/narr/subrequest.rst64
-rw-r--r--docs/narr/templates.rst2
-rw-r--r--docs/narr/testing.rst70
-rw-r--r--docs/narr/upgrading.rst37
-rw-r--r--docs/narr/urldispatch.rst9
-rw-r--r--docs/narr/viewconfig.rst32
-rw-r--r--docs/narr/webob.rst8
-rw-r--r--docs/pscripts/index.rst12
-rw-r--r--docs/pscripts/pcreate.rst13
-rw-r--r--docs/pscripts/pdistreport.rst13
-rw-r--r--docs/pscripts/prequest.rst13
-rw-r--r--docs/pscripts/proutes.rst13
-rw-r--r--docs/pscripts/pserve.rst13
-rw-r--r--docs/pscripts/pshell.rst13
-rw-r--r--docs/pscripts/ptweens.rst13
-rw-r--r--docs/pscripts/pviews.rst13
-rw-r--r--docs/quick_tour.rst995
-rw-r--r--docs/quick_tour/awesome/CHANGES.txt4
-rw-r--r--docs/quick_tour/awesome/MANIFEST.in2
-rw-r--r--docs/quick_tour/awesome/README.txt4
-rw-r--r--docs/quick_tour/awesome/awesome/__init__.py23
-rw-r--r--docs/quick_tour/awesome/awesome/locale/awesome.pot21
-rw-r--r--docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.mobin460 -> 0 bytes
-rw-r--r--docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.po21
-rw-r--r--docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.mobin461 -> 0 bytes
-rw-r--r--docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.po21
-rw-r--r--docs/quick_tour/awesome/awesome/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/quick_tour/awesome/awesome/static/logo.pngbin6641 -> 0 bytes
-rw-r--r--docs/quick_tour/awesome/awesome/static/pylons.css73
-rw-r--r--docs/quick_tour/awesome/awesome/templates/mytemplate.jinja287
-rw-r--r--docs/quick_tour/awesome/awesome/tests.py21
-rw-r--r--docs/quick_tour/awesome/awesome/views.py6
-rw-r--r--docs/quick_tour/awesome/development.ini49
-rw-r--r--docs/quick_tour/awesome/message-extraction.ini3
-rw-r--r--docs/quick_tour/awesome/setup.py36
-rw-r--r--docs/quick_tour/hello_world/app.py2
-rw-r--r--docs/quick_tour/jinja2/app.py2
-rw-r--r--docs/quick_tour/jinja2/views.py2
-rw-r--r--docs/quick_tour/json/app.py4
-rw-r--r--docs/quick_tour/json/hello_world.jinja24
-rw-r--r--docs/quick_tour/json/hello_world.pt17
-rw-r--r--docs/quick_tour/json/views.py4
-rw-r--r--docs/quick_tour/package/MANIFEST.in2
-rw-r--r--docs/quick_tour/package/development.ini63
-rw-r--r--docs/quick_tour/package/hello_world/__init__.py14
-rw-r--r--docs/quick_tour/package/hello_world/init.py34
-rw-r--r--docs/quick_tour/package/hello_world/models.py8
-rw-r--r--docs/quick_tour/package/hello_world/resources.py (renamed from docs/quick_tour/awesome/awesome/models.py)4
-rw-r--r--docs/quick_tour/package/hello_world/static/logo.pngbin6641 -> 0 bytes
-rw-r--r--docs/quick_tour/package/hello_world/static/pylons.css73
-rw-r--r--docs/quick_tour/package/hello_world/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/quick_tour/package/hello_world/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/quick_tour/package/hello_world/static/theme.css153
-rw-r--r--docs/quick_tour/package/hello_world/templates/mytemplate.jinja2158
-rw-r--r--docs/quick_tour/package/hello_world/views.py10
-rw-r--r--docs/quick_tour/package/setup.cfg28
-rw-r--r--docs/quick_tour/package/setup.py27
-rw-r--r--docs/quick_tour/requests/app.py2
-rw-r--r--docs/quick_tour/routing/app.py2
-rw-r--r--docs/quick_tour/routing/views.py2
-rw-r--r--docs/quick_tour/sqla_demo/README.txt6
-rw-r--r--docs/quick_tour/sqla_demo/development.ini4
-rw-r--r--docs/quick_tour/sqla_demo/production.ini2
-rw-r--r--docs/quick_tour/sqla_demo/setup.cfg27
-rw-r--r--docs/quick_tour/sqla_demo/setup.py11
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo.sqlitebin3072 -> 0 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/__init__.py11
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models.py29
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py7
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/meta.py49
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py19
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py30
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/favicon.icobin1406 -> 0 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/footerbg.pngbin333 -> 0 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/headerbg.pngbin203 -> 0 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/ie6.css8
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/middlebg.pngbin2797 -> 0 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/pylons.css372
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-small.pngbin7044 -> 0 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.pngbin33055 -> 12901 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/theme.css154
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/static/transparent.gifbin49 -> 0 bytes
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt)18
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja28
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt76
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/tests.py68
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/views/__init__.py0
-rw-r--r--docs/quick_tour/sqla_demo/sqla_demo/views/default.py (renamed from docs/quick_tour/sqla_demo/sqla_demo/views.py)18
-rw-r--r--docs/quick_tour/static_assets/app.py4
-rw-r--r--docs/quick_tour/static_assets/hello_world.jinja24
-rw-r--r--docs/quick_tour/static_assets/hello_world.pt16
-rw-r--r--docs/quick_tour/static_assets/hello_world_static.jinja210
-rw-r--r--docs/quick_tour/static_assets/views.py2
-rw-r--r--docs/quick_tour/templating/app.py3
-rw-r--r--docs/quick_tour/templating/views.py2
-rw-r--r--docs/quick_tour/view_classes/app.py6
-rw-r--r--docs/quick_tour/view_classes/hello.jinja28
-rw-r--r--docs/quick_tour/view_classes/views.py2
-rw-r--r--docs/quick_tour/views/app.py2
-rw-r--r--docs/quick_tutorial/authentication.rst93
-rw-r--r--docs/quick_tutorial/authorization.rst90
-rw-r--r--docs/quick_tutorial/conf.py281
-rw-r--r--docs/quick_tutorial/databases.rst188
-rw-r--r--docs/quick_tutorial/databases/sqltutorial.sqlitebin12288 -> 0 bytes
-rw-r--r--docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt6
-rw-r--r--docs/quick_tutorial/debugtoolbar.rst62
-rw-r--r--docs/quick_tutorial/forms.rst133
-rw-r--r--docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt6
-rw-r--r--docs/quick_tutorial/functional_testing.rst56
-rw-r--r--docs/quick_tutorial/hello_world.rst83
-rw-r--r--docs/quick_tutorial/index.rst10
-rw-r--r--docs/quick_tutorial/ini.rst96
-rw-r--r--docs/quick_tutorial/jinja2.rst59
-rw-r--r--docs/quick_tutorial/json.rst89
-rw-r--r--docs/quick_tutorial/logging.rst54
-rw-r--r--docs/quick_tutorial/more_view_classes.rst133
-rw-r--r--docs/quick_tutorial/package.rst78
-rw-r--r--docs/quick_tutorial/request_response.rst73
-rw-r--r--docs/quick_tutorial/requirements.rst231
-rw-r--r--docs/quick_tutorial/routing.rst71
-rw-r--r--docs/quick_tutorial/scaffolds.rst55
-rw-r--r--docs/quick_tutorial/sessions.rst72
-rw-r--r--docs/quick_tutorial/static_assets.rst47
-rw-r--r--docs/quick_tutorial/templating.rst87
-rw-r--r--docs/quick_tutorial/tutorial_approach.rst66
-rw-r--r--docs/quick_tutorial/unit_testing.rst118
-rw-r--r--docs/quick_tutorial/view_classes.rst80
-rw-r--r--docs/quick_tutorial/views.rst87
-rw-r--r--docs/tutorials/modwsgi/index.rst48
-rw-r--r--docs/tutorials/wiki/authorization.rst44
-rw-r--r--docs/tutorials/wiki/background.rst2
-rw-r--r--docs/tutorials/wiki/basiclayout.rst33
-rw-r--r--docs/tutorials/wiki/definingmodels.rst9
-rw-r--r--docs/tutorials/wiki/definingviews.rst39
-rw-r--r--docs/tutorials/wiki/design.rst6
-rw-r--r--docs/tutorials/wiki/distributing.rst21
-rw-r--r--docs/tutorials/wiki/index.rst1
-rw-r--r--docs/tutorials/wiki/installation.rst386
-rw-r--r--docs/tutorials/wiki/src/authorization/CHANGES.txt3
-rw-r--r--docs/tutorials/wiki/src/authorization/README.txt8
-rw-r--r--docs/tutorials/wiki/src/authorization/development.ini8
-rw-r--r--docs/tutorials/wiki/src/authorization/production.ini6
-rw-r--r--docs/tutorials/wiki/src/authorization/setup.py21
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt5
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/tests.py124
-rw-r--r--docs/tutorials/wiki/src/authorization/tutorial/views.py33
-rw-r--r--docs/tutorials/wiki/src/basiclayout/README.txt8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/development.ini8
-rw-r--r--docs/tutorials/wiki/src/basiclayout/production.ini6
-rw-r--r--docs/tutorials/wiki/src/basiclayout/setup.py21
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt5
-rw-r--r--docs/tutorials/wiki/src/basiclayout/tutorial/tests.py1
-rw-r--r--docs/tutorials/wiki/src/installation/CHANGES.txt4
-rw-r--r--docs/tutorials/wiki/src/installation/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki/src/installation/README.txt12
-rw-r--r--docs/tutorials/wiki/src/installation/development.ini65
-rw-r--r--docs/tutorials/wiki/src/installation/production.ini60
-rw-r--r--docs/tutorials/wiki/src/installation/setup.py53
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/__init__.py18
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/models.py14
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt (renamed from docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt)9
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/tests.py17
-rw-r--r--docs/tutorials/wiki/src/installation/tutorial/views.py7
-rw-r--r--docs/tutorials/wiki/src/models/CHANGES.txt2
-rw-r--r--docs/tutorials/wiki/src/models/README.txt8
-rw-r--r--docs/tutorials/wiki/src/models/development.ini8
-rw-r--r--docs/tutorials/wiki/src/models/production.ini6
-rw-r--r--docs/tutorials/wiki/src/models/setup.py21
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt5
-rw-r--r--docs/tutorials/wiki/src/models/tutorial/tests.py44
-rw-r--r--docs/tutorials/wiki/src/tests/CHANGES.txt3
-rw-r--r--docs/tutorials/wiki/src/tests/README.txt8
-rw-r--r--docs/tutorials/wiki/src/tests/development.ini8
-rw-r--r--docs/tutorials/wiki/src/tests/production.ini6
-rw-r--r--docs/tutorials/wiki/src/tests/setup.py22
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/__init__.py2
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt5
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/tests.py4
-rw-r--r--docs/tutorials/wiki/src/tests/tutorial/views.py33
-rw-r--r--docs/tutorials/wiki/src/views/CHANGES.txt5
-rw-r--r--docs/tutorials/wiki/src/views/README.txt8
-rw-r--r--docs/tutorials/wiki/src/views/development.ini8
-rw-r--r--docs/tutorials/wiki/src/views/production.ini6
-rw-r--r--docs/tutorials/wiki/src/views/setup.py21
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/models.py2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt5
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/templates/view.pt2
-rw-r--r--docs/tutorials/wiki/src/views/tutorial/tests.py127
-rw-r--r--docs/tutorials/wiki/tests.rst47
-rw-r--r--docs/tutorials/wiki2/authentication.rst312
-rw-r--r--docs/tutorials/wiki2/authorization.rst501
-rw-r--r--docs/tutorials/wiki2/background.rst6
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst398
-rw-r--r--docs/tutorials/wiki2/definingmodels.rst288
-rw-r--r--docs/tutorials/wiki2/definingviews.rst494
-rw-r--r--docs/tutorials/wiki2/design.rst260
-rw-r--r--docs/tutorials/wiki2/distributing.rst20
-rw-r--r--docs/tutorials/wiki2/index.rst13
-rw-r--r--docs/tutorials/wiki2/installation.rst484
-rw-r--r--docs/tutorials/wiki2/src/authentication/CHANGES.txt4
-rw-r--r--docs/tutorials/wiki2/src/authentication/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/authentication/README.txt14
-rw-r--r--docs/tutorials/wiki2/src/authentication/development.ini73
-rw-r--r--docs/tutorials/wiki2/src/authentication/production.ini62
-rw-r--r--docs/tutorials/wiki2/src/authentication/setup.py57
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/__init__.py13
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py74
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py16
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/models/page.py20
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/models/user.py29
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/routes.py8
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/scripts/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/scripts/initializedb.py57
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/security.py27
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/templates/404.jinja28
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/templates/edit.jinja220
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/views/tutorial/templates/view.pt)37
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/templates/login.jinja226
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/templates/view.jinja218
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/tests.py65
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py46
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/default.py79
-rw-r--r--docs/tutorials/wiki2/src/authentication/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki2/src/authorization/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/authorization/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/authorization/development.ini10
-rw-r--r--docs/tutorials/wiki2/src/authorization/production.ini18
-rw-r--r--docs/tutorials/wiki2/src/authorization/setup.py25
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py34
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models.py37
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py74
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py16
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/page.py20
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/user.py29
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/routes.py56
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py44
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/security.py45
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/404.jinja28
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.jinja220
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt72
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt)40
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/login.jinja226
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt74
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/view.jinja218
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/tests.py175
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views.py124
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py46
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/default.py64
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/production.ini8
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/setup.py22
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py15
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models.py27
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py73
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py16
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py18
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/routes.py3
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py30
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/404.jinja28
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt)18
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja28
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py66
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py (renamed from docs/tutorials/wiki2/src/models/tutorial/views.py)18
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki2/src/installation/CHANGES.txt4
-rw-r--r--docs/tutorials/wiki2/src/installation/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/installation/README.txt14
-rw-r--r--docs/tutorials/wiki2/src/installation/development.ini71
-rw-r--r--docs/tutorials/wiki2/src/installation/production.ini60
-rw-r--r--docs/tutorials/wiki2/src/installation/setup.py55
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/__init__.py12
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py73
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/models/meta.py16
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/models/mymodel.py18
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/routes.py3
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/scripts/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/scripts/initializedb.py45
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/static/pyramid-16x16.pngbin0 -> 1319 bytes
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/static/pyramid.pngbin0 -> 12901 bytes
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/static/theme.css154
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/templates/404.jinja28
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt)18
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja28
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/tests.py65
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/views/default.py (renamed from docs/tutorials/wiki2/src/basiclayout/tutorial/views.py)18
-rw-r--r--docs/tutorials/wiki2/src/installation/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki2/src/models/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/models/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/models/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/models/production.ini16
-rw-r--r--docs/tutorials/wiki2/src/models/setup.py23
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/__init__.py15
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models.py25
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/__init__.py74
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/meta.py16
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/page.py20
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/user.py29
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/routes.py3
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py44
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja28
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja266
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja28
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/tests.py66
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/views/default.py33
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki2/src/tests/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/tests/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/tests/development.ini10
-rw-r--r--docs/tutorials/wiki2/src/tests/production.ini18
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py26
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/__init__.py34
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models.py37
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py74
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/meta.py16
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/page.py20
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/user.py29
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/routes.py56
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py44
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/security.py45
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/404.jinja28
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja220
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt74
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt)37
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja226
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt54
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja218
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests.py235
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py122
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py168
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views.py123
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/auth.py46
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/default.py64
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki2/src/views/MANIFEST.in2
-rw-r--r--docs/tutorials/wiki2/src/views/README.txt2
-rw-r--r--docs/tutorials/wiki2/src/views/development.ini8
-rw-r--r--docs/tutorials/wiki2/src/views/production.ini16
-rw-r--r--docs/tutorials/wiki2/src/views/setup.py25
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/__init__.py18
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models.py25
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/__init__.py74
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/meta.py16
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/page.py20
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/user.py29
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/routes.py6
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py44
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/static/theme.min.css1
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/404.jinja28
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/edit.jinja220
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/tests/tutorial/templates/view.pt)33
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt66
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/view.jinja218
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/tests.py175
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views.py72
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/default.py73
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/notfound.py7
-rw-r--r--docs/tutorials/wiki2/tests.rst109
-rw-r--r--docs/whatsnew-1.0.rst2
-rw-r--r--docs/whatsnew-1.1.rst2
-rw-r--r--docs/whatsnew-1.2.rst2
-rw-r--r--docs/whatsnew-1.3.rst13
-rw-r--r--docs/whatsnew-1.4.rst2
-rw-r--r--docs/whatsnew-1.5.rst4
-rw-r--r--docs/whatsnew-1.6.rst218
-rw-r--r--docs/whatsnew-1.7.rst190
444 files changed, 12201 insertions, 8599 deletions
diff --git a/docs/Makefile b/docs/Makefile
index 546deb30a..411ff35df 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -12,16 +12,20 @@ PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
+.PHONY: help clean html text web pickle htmlhelp latex latexpdf changes linkcheck epub doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " pickle to make pickle files (usable by e.g. sphinx-web)"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " changes to make an overview over all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
+ @echo " html to make standalone HTML files"
+ @echo " text to make text files"
+ @echo " pickle to make pickle files (usable by e.g. sphinx-web)"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " changes to make an overview over all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " epub to make an epub"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
@@ -90,3 +94,7 @@ epub:
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/_static/pyramid_request_processing.graffle b/docs/_static/pyramid_request_processing.graffle
index 71319610b..56e4e13f2 100644
--- a/docs/_static/pyramid_request_processing.graffle
+++ b/docs/_static/pyramid_request_processing.graffle
@@ -22,7 +22,7 @@
<key>Font</key>
<string>Helvetica</string>
<key>Size</key>
- <real>12</real>
+ <real>13</real>
</dict>
<key>ID</key>
<integer>2</integer>
@@ -53,12 +53,187 @@
<key>Creator</key>
<string>Steve Piercy</string>
<key>DisplayScale</key>
- <string>1 0/72 in = 1 0/72 in</string>
+ <string>1 0/72 in = 1.0000 in</string>
<key>GraphDocumentVersion</key>
<integer>8</integer>
<key>GraphicsList</key>
<array>
<dict>
+ <key>Bounds</key>
+ <string>{{238.74999618530273, 294.65604172230951}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
+ <key>ID</key>
+ <integer>169515</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 CSRF checks}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>12</real>
+ </dict>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>169513</integer>
+ </dict>
+ <key>ID</key>
+ <integer>169514</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Points</key>
+ <array>
+ <string>{154.9999760464211, 209.11365574251681}</string>
+ <string>{239.8333613077798, 209.14732074737549}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.0980392</string>
+ <key>g</key>
+ <string>0.0980392</string>
+ <key>r</key>
+ <string>0.0980392</string>
+ </dict>
+ <key>HeadArrow</key>
+ <string>0</string>
+ <key>Legacy</key>
+ <true/>
+ <key>Pattern</key>
+ <integer>2</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>169373</integer>
+ <key>Position</key>
+ <real>0.47711458802223206</real>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{239.83336130777977, 197.875}, {105.66666412353516, 22.544641494750977}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>ID</key>
+ <integer>169513</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>b</key>
+ <string>0.999449</string>
+ <key>g</key>
+ <string>0.743511</string>
+ <key>r</key>
+ <string>0.872276</string>
+ </dict>
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
+
+\f0\fs20 \cf0 BeforeTraversal}</string>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
<key>Class</key>
<string>LineGraphic</string>
<key>FontInfo</key>
@@ -84,8 +259,8 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{344.41667175292969, 402.88506673894034}</string>
- <string>{375.5, 402.27232108797347}</string>
+ <string>{344.41668319702148, 411.88506673894034}</string>
+ <string>{375.5, 411.77232108797347}</string>
</array>
<key>Style</key>
<dict>
@@ -113,7 +288,7 @@
<key>Tail</key>
<dict>
<key>ID</key>
- <integer>169428</integer>
+ <integer>169509</integer>
</dict>
</dict>
<dict>
@@ -237,378 +412,430 @@
</dict>
</dict>
<dict>
+ <key>Bounds</key>
+ <string>{{238.74999618530273, 275.99999999999994}, {105.75002924601222, 18.656048080136394}}</string>
<key>Class</key>
- <string>Group</string>
- <key>Graphics</key>
+ <string>ShapedGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
+ <key>ID</key>
+ <integer>169506</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
<array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
<dict>
- <key>Bounds</key>
- <string>{{238.8333613077798, 284.99999999999994}, {105.66668701171875, 18.656048080136394}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>ID</key>
- <integer>169425</integer>
- <key>Magnets</key>
- <array>
- <string>{0, 1}</string>
- <string>{0, -1}</string>
- <string>{1, 0}</string>
- <string>{-1, 0}</string>
- <string>{0.50000000000000089, -0.49999999999999645}</string>
- <string>{-0.49526813868737474, -0.4689979626999552}</string>
- </array>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
+ <key>Color</key>
<dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.637876</string>
- <key>g</key>
- <string>1</string>
- <key>r</key>
- <string>1</string>
- </dict>
- </dict>
- <key>shadow</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- <key>ShadowVector</key>
- <string>{2, 2}</string>
- </dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
</dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 authorization}</string>
- <key>VerticalPad</key>
- <integer>0</integer>
- </dict>
- </dict>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.74999618530273, 421.15071036499205}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
+ <key>ID</key>
+ <integer>169507</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, 0.5}</string>
+ <string>{-0.49999999999999911, 0.49999999999999289}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
<dict>
- <key>Bounds</key>
- <string>{{238.75000762939453, 412.15071036499205}, {105.66666412353516, 18.656048080136394}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>ID</key>
- <integer>169426</integer>
- <key>Magnets</key>
- <array>
- <string>{0, 1}</string>
- <string>{0, -1}</string>
- <string>{1, 0}</string>
- <string>{-1, 0}</string>
- <string>{0.50000000000000089, 0.5}</string>
- <string>{-0.49999999999999911, 0.49999999999999289}</string>
- </array>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
+ <key>Color</key>
<dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.637876</string>
- <key>g</key>
- <string>1</string>
- <key>r</key>
- <string>1</string>
- </dict>
- </dict>
- <key>shadow</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- <key>ShadowVector</key>
- <string>{2, 2}</string>
- </dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
</dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 decorators egress}</string>
- <key>VerticalPad</key>
- <integer>0</integer>
- </dict>
- </dict>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.74999618530273, 312.65604172230951}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
+ <key>ID</key>
+ <integer>169508</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ <string>{0.50000000000000089, -0.49999999999999645}</string>
+ <string>{-0.49526813868737474, -0.4689979626999552}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
<dict>
- <key>Bounds</key>
- <string>{{238.75000762939453, 303.65604172230951}, {105.66666412353516, 18.656048080136394}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>ID</key>
- <integer>169427</integer>
- <key>Magnets</key>
- <array>
- <string>{0, 1}</string>
- <string>{0, -1}</string>
- <string>{1, 0}</string>
- <string>{-1, 0}</string>
- <string>{0.50000000000000089, -0.49999999999999645}</string>
- <string>{-0.49526813868737474, -0.4689979626999552}</string>
- </array>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
+ <key>Color</key>
<dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.637876</string>
- <key>g</key>
- <string>1</string>
- <key>r</key>
- <string>1</string>
- </dict>
- </dict>
- <key>shadow</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- <key>ShadowVector</key>
- <string>{2, 2}</string>
- </dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
</dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 decorators ingress}</string>
- <key>VerticalPad</key>
- <integer>0</integer>
- </dict>
- </dict>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.74999618530273, 402.55704269887212}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
+ <key>ID</key>
+ <integer>169509</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
<dict>
- <key>Bounds</key>
- <string>{{238.75000762939453, 393.55704269887212}, {105.66666412353516, 18.656048080136394}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>ID</key>
- <integer>169428</integer>
- <key>Magnets</key>
- <array>
- <string>{0, 1}</string>
- <string>{0, -1}</string>
- <string>{1, 0}</string>
- <string>{-1, 0}</string>
- </array>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
+ <key>Color</key>
<dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.637876</string>
- <key>g</key>
- <string>1</string>
- <key>r</key>
- <string>1</string>
- </dict>
- </dict>
- <key>shadow</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- <key>ShadowVector</key>
- <string>{2, 2}</string>
- </dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
</dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 response adapter}</string>
- <key>VerticalPad</key>
- <integer>0</integer>
- </dict>
- </dict>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.74999618530273, 383.90099016834085}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
+ <key>ID</key>
+ <integer>169510</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
<dict>
- <key>Bounds</key>
- <string>{{238.75000762939453, 374.90099016834085}, {105.66666412353516, 18.656048080136394}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>ID</key>
- <integer>169429</integer>
- <key>Magnets</key>
- <array>
- <string>{0, 1}</string>
- <string>{0, -1}</string>
- <string>{1, 0}</string>
- <string>{-1, 0}</string>
- </array>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
+ <key>Color</key>
<dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.637876</string>
- <key>g</key>
- <string>1</string>
- <key>r</key>
- <string>1</string>
- </dict>
- </dict>
- <key>shadow</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- <key>ShadowVector</key>
- <string>{2, 2}</string>
- </dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
</dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 view mapper egress}</string>
- <key>VerticalPad</key>
- <integer>0</integer>
- </dict>
- </dict>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.74999618530273, 350.36561209044055}, {105.66668701171875, 33.089282989501953}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
+ <key>ID</key>
+ <integer>169511</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
<dict>
- <key>Bounds</key>
- <string>{{238.75000762939453, 341.36561209044055}, {105.66666412353516, 33.089282989501953}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>ID</key>
- <integer>169430</integer>
- <key>Magnets</key>
- <array>
- <string>{0, 1}</string>
- <string>{0, -1}</string>
- <string>{1, 0}</string>
- <string>{-1, 0}</string>
- </array>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
+ <key>Color</key>
<dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.422927</string>
- <key>g</key>
- <string>1</string>
- <key>r</key>
- <string>1</string>
- </dict>
- </dict>
- <key>shadow</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- <key>ShadowVector</key>
- <string>{2, 2}</string>
- </dict>
+ <key>b</key>
+ <string>0.422927</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
</dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 view}</string>
- <key>VerticalPad</key>
- <integer>0</integer>
- </dict>
- </dict>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
+ <string>{{238.74999618530273, 331.26348241170439}, {105.66668701171875, 18.656048080136394}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
+ <key>ID</key>
+ <integer>169512</integer>
+ <key>Layer</key>
+ <integer>0</integer>
+ <key>Magnets</key>
+ <array>
+ <string>{0, 1}</string>
+ <string>{0, -1}</string>
+ <string>{1, 0}</string>
+ <string>{-1, 0}</string>
+ </array>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>fill</key>
<dict>
- <key>Bounds</key>
- <string>{{238.75000762939453, 322.26348241170439}, {105.66666412353516, 18.656048080136394}}</string>
- <key>Class</key>
- <string>ShapedGraphic</string>
- <key>ID</key>
- <integer>169431</integer>
- <key>Magnets</key>
- <array>
- <string>{0, 1}</string>
- <string>{0, -1}</string>
- <string>{1, 0}</string>
- <string>{-1, 0}</string>
- </array>
- <key>Shape</key>
- <string>Rectangle</string>
- <key>Style</key>
+ <key>Color</key>
<dict>
- <key>fill</key>
- <dict>
- <key>Color</key>
- <dict>
- <key>b</key>
- <string>0.637876</string>
- <key>g</key>
- <string>1</string>
- <key>r</key>
- <string>1</string>
- </dict>
- </dict>
- <key>shadow</key>
- <dict>
- <key>Draws</key>
- <string>NO</string>
- <key>ShadowVector</key>
- <string>{2, 2}</string>
- </dict>
+ <key>b</key>
+ <string>0.637876</string>
+ <key>g</key>
+ <string>1</string>
+ <key>r</key>
+ <string>1</string>
</dict>
- <key>Text</key>
- <dict>
- <key>Text</key>
- <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
+ </dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ <key>ShadowVector</key>
+ <string>{2, 2}</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf400
\cocoascreenfonts1{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
\f0\fs20 \cf0 view mapper ingress}</string>
- <key>VerticalPad</key>
- <integer>0</integer>
- </dict>
- </dict>
- </array>
- <key>ID</key>
- <integer>169424</integer>
- <key>Layer</key>
- <integer>0</integer>
+ <key>VerticalPad</key>
+ <integer>0</integer>
+ </dict>
</dict>
<dict>
<key>Class</key>
@@ -1094,7 +1321,7 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{238.75000762939462, 430.80675844512831}</string>
+ <string>{238.74999618530282, 439.80675844512831}</string>
<string>{207.66666666666765, 385.656005859375}</string>
</array>
<key>Style</key>
@@ -1123,7 +1350,7 @@
<key>Tail</key>
<dict>
<key>ID</key>
- <integer>169426</integer>
+ <integer>169507</integer>
<key>Info</key>
<integer>6</integer>
</dict>
@@ -1144,7 +1371,7 @@
<integer>0</integer>
<key>Points</key>
<array>
- <string>{239.33336141608385, 285.57837549845181}</string>
+ <string>{239.25039065750093, 276.57837549845181}</string>
<string>{207.66666666666777, 353.07514659563753}</string>
</array>
<key>Style</key>
@@ -1173,7 +1400,7 @@
<key>Tail</key>
<dict>
<key>ID</key>
- <integer>169425</integer>
+ <integer>169506</integer>
<key>Info</key>
<integer>6</integer>
</dict>
@@ -1515,7 +1742,7 @@
{\colortbl;\red255\green255\blue255;}
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc
-\f0\fs20 \cf0 view}</string>
+\f0\fs20 \cf0 view deriver}</string>
<key>VerticalPad</key>
<integer>0</integer>
</dict>
@@ -1742,7 +1969,7 @@
</dict>
<dict>
<key>Bounds</key>
- <string>{{375.5, 391}, {105.66666412353516, 22.544642175946908}}</string>
+ <string>{{375.5, 400.5}, {105.66666412353516, 22.544642175946908}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>ID</key>
@@ -9578,6 +9805,10 @@
<string>YES</string>
<key>HPages</key>
<integer>1</integer>
+ <key>HorizontalGuides</key>
+ <array>
+ <real>209.875</real>
+ </array>
<key>ImageCounter</key>
<integer>3</integer>
<key>KeepToScale</key>
@@ -9637,7 +9868,7 @@
<key>MasterSheets</key>
<array/>
<key>ModificationDate</key>
- <string>2014-11-23 07:19:11 +0000</string>
+ <string>2016-04-13 08:32:47 +0000</string>
<key>Modifier</key>
<string>Steve Piercy</string>
<key>NotesVisible</key>
@@ -9718,7 +9949,7 @@
</dict>
</array>
<key>Frame</key>
- <string>{{35, 93}, {1394, 1325}}</string>
+ <string>{{35, 93}, {2284, 1325}}</string>
<key>ListView</key>
<true/>
<key>OutlineWidth</key>
@@ -9732,15 +9963,15 @@
<key>SidebarWidth</key>
<integer>163</integer>
<key>VisibleRegion</key>
- <string>{{-231, -226}, {1037, 1186}}</string>
+ <string>{{110.125, 77.875}, {239.125, 146.375}}</string>
<key>Zoom</key>
- <real>1</real>
+ <real>8</real>
<key>ZoomValues</key>
<array>
<array>
<string>Request Processing</string>
- <real>1</real>
- <real>2</real>
+ <real>8</real>
+ <real>4</real>
</array>
</array>
</dict>
diff --git a/docs/_static/pyramid_request_processing.png b/docs/_static/pyramid_request_processing.png
index 2fbb1e164..2f44f4824 100644
--- a/docs/_static/pyramid_request_processing.png
+++ b/docs/_static/pyramid_request_processing.png
Binary files differ
diff --git a/docs/_static/pyramid_request_processing.svg b/docs/_static/pyramid_request_processing.svg
index 21bbcb532..03f6d56fa 100644
--- a/docs/_static/pyramid_request_processing.svg
+++ b/docs/_static/pyramid_request_processing.svg
@@ -1,3 +1,3 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="91 11 424 533" width="424pt" height="533pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2014-11-23 07:19Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Request Processing</title><rect fill="white" width="576" height="733"/><g><title>no exceptions</title><path d="M 155 444.75674 C 155 450.64061 155 486.2592 155 502.71617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 322.33334 C 154.99999 327.72413 155 337.74646 155 346.1775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 245.22768 C 154.99999 250.5417 154.99999 257.93189 154.99999 265.10145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 198.62203 C 154.99995 203.74682 154.99998 209.1909 154.99999 215.28222" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 50.455358)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="4.7596016" y="10" textLength="88.92578">middleware ingress </tspan></text><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 101.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.983723" y="10" textLength="61.69922">tween ingress</tspan></text><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 227.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="28.660969" y="10" textLength="38.344727">traversal</tspan></text><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 252.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.424641" y="10" textLength="62.817383">ContextFound</tspan></text><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 427.48442)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.094563" y="10" textLength="59.47754">tween egress</tspan></text><rect x="239" y="445.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="445.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 450.50821)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3113594" y="10" textLength="85.043945">response callbacks</tspan></text><rect x="239" y="497.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="497.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 502.5082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.6463203" y="10" textLength="5">fi</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="13.64632" y="10" textLength="73.374023">nished callbacks</tspan></text><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 514.89027)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.8704414" y="10" textLength="83.92578">middleware egress</tspan></text><path d="M 155 67.72768 C 155 73.048893 155 81.55558 155 89.2853" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 119.22768 C 155 124.62026 154.99997 133.48763 154.99996 141.38632" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="391" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="391" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 396.27232)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.702961" y="10" textLength="62.260742">BeforeRender</tspan></text><text transform="translate(233.5 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".31445312" y="11" textLength="115.371094">Request Processing</tspan></text><path d="M 375.99995 42.910746 L 498.66662 42.910746 C 501.42805 42.910746 503.66662 45.149323 503.66662 47.910746 L 503.66662 222 C 503.66662 224.76142 501.42805 227 498.66662 227 L 375.99995 227 C 373.23853 227 370.99995 224.76142 370.99995 222 L 370.99995 47.910746 C 370.99995 45.149323 373.23853 42.910746 375.99995 42.910746 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(375.99995 42.910746)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="0" y="10" textLength="35.55664">Legend</tspan></text><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 69.180834)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.601887" y="10" textLength="24.46289">event</tspan></text><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" fill="#fed153"/><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 191.85458)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.769367" y="10" textLength="36.12793">callback</tspan></text><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" fill="#ffff6c"/><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 163.8223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.83089" y="10" textLength="20.004883">view</tspan></text><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" fill="#a4cfff"/><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 96.48543)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.148762" y="10" textLength="76.14746">external process </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="2.8162422" y="22" textLength="90.03418">(middleware, tween)</tspan></text><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 135.79003)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.537922" y="10" textLength="70.59082">internal process</tspan></text><line x1="154.99999" y1="258.44082" x2="238.83336" y2="258.45536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 363.61979)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.205402" y="10" textLength="57.25586">view pipeline</tspan></text><path d="M 155 386.66443 C 155 392.17252 155 405.5052 155 415.30935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.33336" y1="285.57838" x2="207.66667" y2="353.07515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75001" y1="430.80676" x2="207.66667" y2="385.656" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 307.71132)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="24.764484" y="10" textLength="46.137695">predicates</tspan></text><rect x="102.16666" y="272" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="272" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 282.54465)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.707844" y="10" textLength="52.250977">view lookup</tspan></text><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 184)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.978855" y="10" textLength="71.708984">route predicates</tspan></text><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 158.83333)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.001804" y="10" textLength="20.004883">URL</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.640476" y="10" textLength="40.024414"> dispatch</tspan></text><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 122.59152)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.207844" y="10" textLength="57.250977">NewRequest</tspan></text><line x1="154.99999" y1="128.68025" x2="239.8334" y2="128.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 476.49852)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="15.316242" y="10" textLength="65.03418">NewResponse</tspan></text><line x1="155" y1="470.25295" x2="238.33861" y2="482.42625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75001" y="322.26348" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="322.26348" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 325.5915)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="1.9812813" y="10" textLength="91.7041">view mapper ingress</tspan></text><rect x="238.75001" y="341.36561" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="238.75001" y="341.36561" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 351.91025)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.83089" y="10" textLength="20.004883">view</tspan></text><rect x="238.75001" y="374.901" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="374.901" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 378.22901)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="3.0921211" y="10" textLength="89.48242">view mapper egress</tspan></text><rect x="238.75001" y="393.55704" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="393.55704" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 396.88507)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.9173164" y="10" textLength="77.83203">response adapter</tspan></text><rect x="238.75001" y="303.65604" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="303.65604" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 306.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="6.702961" y="10" textLength="82.26074">decorators ingress</tspan></text><rect x="238.75001" y="412.1507" width="105.666664" height="18.656048" fill="#ffffa3"/><rect x="238.75001" y="412.1507" width="105.666664" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75001 415.47873)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="7.813801" y="10" textLength="80.039062">decorators egress</tspan></text><rect x="238.83336" y="285" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.83336" y="285" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 288.32802)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.202972" y="10" textLength="57.260742">authorization</tspan></text><line x1="155" y1="482.12575" x2="238.52297" y2="508.3584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="459.27668" x2="238.50027" y2="456.52468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41667" y1="402.88507" x2="375.5" y2="402.27232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/></g></g></svg>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="91 11 424 533" width="424pt" height="533pt"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:date>2016-04-13 08:32Z</dc:date><!-- Produced by OmniGraffle Professional 5.4.4 --></metadata><defs><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="#191919"><g><path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="10" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Request Processing</title><rect fill="white" width="576" height="733"/><g><title>no exceptions</title><path d="M 155 444.75674 C 155 450.64061 155 486.2592 155 502.71617" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 322.33334 C 154.99999 327.72413 155 337.74646 155 346.1775" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99999 245.22768 C 154.99999 250.5417 154.99999 257.93189 154.99999 265.10145" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 154.99995 198.62203 C 154.99995 203.74682 154.99998 209.1909 154.99999 215.28222" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="45.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 50.455358)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="4.7596016" y="10" textLength="88.92578">middleware ingress </tspan></text><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="96.183037" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 101.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.983723" y="10" textLength="61.69922">tween ingress</tspan></text><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="102.16667" y="222.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 227.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="28.660969" y="10" textLength="38.344727">traversal</tspan></text><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="247.18304" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 252.45536)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.424641" y="10" textLength="62.817383">ContextFound</tspan></text><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="422.2121" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 427.48442)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.094563" y="10" textLength="59.47754">tween egress</tspan></text><rect x="239" y="445.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="445.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 450.50821)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.3113594" y="10" textLength="85.043945">response callbacks</tspan></text><rect x="239" y="497.2359" width="105.666664" height="22.544641" fill="#fed153"/><rect x="239" y="497.2359" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244 502.5082)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.6463203" y="10" textLength="5">fi</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="13.64632" y="10" textLength="73.374023">nished callbacks</tspan></text><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" fill="#a4cfff"/><rect x="102.16667" y="509.61795" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 514.89027)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="5.8704414" y="10" textLength="83.92578">middleware egress</tspan></text><path d="M 155 67.72768 C 155 73.048893 155 81.55558 155 89.2853" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><path d="M 155 119.22768 C 155 124.62026 154.99997 133.48763 154.99996 141.38632" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><rect x="375.5" y="400.5" width="105.666664" height="22.544642" fill="#dfbeff"/><rect x="375.5" y="400.5" width="105.666664" height="22.544642" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(380.5 405.77232)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="16.702961" y="10" textLength="62.260742">BeforeRender</tspan></text><text transform="translate(233.5 20)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".31445312" y="11" textLength="115.371094">Request Processing</tspan></text><path d="M 375.99995 42.910746 L 498.66662 42.910746 C 501.42805 42.910746 503.66662 45.149323 503.66662 47.910746 L 503.66662 222 C 503.66662 224.76142 501.42805 227 498.66662 227 L 375.99995 227 C 373.23853 227 370.99995 224.76142 370.99995 222 L 370.99995 47.910746 C 370.99995 45.149323 373.23853 42.910746 375.99995 42.910746 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(375.99995 42.910746)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="bold" x="0" y="10" textLength="35.55664">Legend</tspan></text><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="383.66662" y="63.908513" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 69.180834)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="35.601887" y="10" textLength="24.46289">event</tspan></text><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" fill="#fed153"/><rect x="383.66662" y="186.58226" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 191.85458)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="29.769367" y="10" textLength="36.12793">callback</tspan></text><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" fill="#ffff6c"/><rect x="383.66662" y="158.54998" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 163.8223)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.158527" y="10" textLength="53.34961">view deriver</tspan></text><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" fill="#a4cfff"/><rect x="383.66662" y="91.94079" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 96.48543)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.148762" y="10" textLength="76.14746">external process </tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="2.8162422" y="22" textLength="90.03418">(middleware, tween)</tspan></text><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" fill="#d2ffd0"/><rect x="383.66662" y="130.51771" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(388.66662 135.79003)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.537922" y="10" textLength="70.59082">internal process</tspan></text><line x1="154.99999" y1="258.44082" x2="238.83336" y2="258.45536" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" fill="#ffff6c"/><rect x="102.16667" y="353.07515" width="105.666664" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16667 363.61979)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.205402" y="10" textLength="57.25586">view pipeline</tspan></text><path d="M 155 386.66443 C 155 392.17252 155 405.5052 155 415.30935" marker-end="url(#SharpArrow_Marker)" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><line x1="239.25039" y1="276.57838" x2="207.66667" y2="353.07515" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><line x1="238.75" y1="439.80676" x2="207.66667" y2="385.656" stroke="#c1c1c1" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,3"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" fill="#d2ffd0"/><rect x="102.16666" y="305.0893" width="105.666664" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 307.71132)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="24.764484" y="10" textLength="46.137695">predicates</tspan></text><rect x="102.16666" y="272" width="105.666664" height="33.089294" fill="#d2ffd0"/><rect x="102.16666" y="272" width="105.666664" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.16666 282.54465)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="21.707844" y="10" textLength="52.250977">view lookup</tspan></text><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" fill="#d2ffd0"/><rect x="102.166606" y="181.37798" width="105.666695" height="17.244049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 184)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="11.978855" y="10" textLength="71.708984">route predicates</tspan></text><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" fill="#d2ffd0"/><rect x="102.166606" y="148.28869" width="105.666695" height="33.089294" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(107.166606 158.83333)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="18.001804" y="10" textLength="20.004883">URL</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.640476" y="10" textLength="40.024414"> dispatch</tspan></text><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.8334" y="117.3192" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.8334 122.59152)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.207844" y="10" textLength="57.250977">NewRequest</tspan></text><line x1="154.99999" y1="128.68025" x2="239.8334" y2="128.59152" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="238.83336" y="471.2262" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.83336 476.49852)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="15.316242" y="10" textLength="65.03418">NewResponse</tspan></text><line x1="155" y1="470.25295" x2="238.33861" y2="482.42625" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="331.26348" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="331.26348" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 334.5915)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="1.9812927" y="10" textLength="91.7041">view mapper ingress</tspan></text><rect x="238.75" y="350.36561" width="105.66669" height="33.089283" fill="#ffff6c"/><rect x="238.75" y="350.36561" width="105.66669" height="33.089283" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 360.91025)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="37.830902" y="10" textLength="20.004883">view</tspan></text><rect x="238.75" y="383.901" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="383.901" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 387.22901)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="3.0921326" y="10" textLength="89.48242">view mapper egress</tspan></text><rect x="238.75" y="402.55704" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="402.55704" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 405.88507)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="8.917328" y="10" textLength="77.83203">response adapter</tspan></text><rect x="238.75" y="312.65604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="312.65604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 315.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="6.7029724" y="10" textLength="82.26074">decorators ingress</tspan></text><rect x="238.75" y="421.1507" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="421.1507" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 424.47873)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="7.8138123" y="10" textLength="80.039062">decorators egress</tspan></text><rect x="238.75" y="276" width="105.75003" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="276" width="105.75003" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 279.32802)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="19.244644" y="10" textLength="57.260742">authorization</tspan></text><line x1="155" y1="482.12575" x2="238.52297" y2="508.3584" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="155" y1="459.27668" x2="238.50027" y2="456.52468" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><line x1="344.41668" y1="411.88507" x2="375.5" y2="411.77232" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="239.83336" y="197.875" width="105.666664" height="22.544641" fill="#dfbeff"/><rect x="239.83336" y="197.875" width="105.666664" height="22.544641" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(244.83336 203.14732)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="12.44759" y="10" textLength="35.57129">BeforeT</tspan><tspan font-family="Helvetica" font-size="10" font-weight="500" x="47.652668" y="10" textLength="35.566406">raversal</tspan></text><line x1="154.99998" y1="209.11366" x2="239.83336" y2="209.14732" stroke="#191919" stroke-linecap="round" stroke-linejoin="round" stroke-width="1" stroke-dasharray="1,4"/><rect x="238.75" y="294.65604" width="105.66669" height="18.656048" fill="#ffffa3"/><rect x="238.75" y="294.65604" width="105.66669" height="18.656048" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/><text transform="translate(243.75 297.98407)" fill="black"><tspan font-family="Helvetica" font-size="10" font-weight="500" x="17.27182" y="10" textLength="61.123047">CSRF checks</tspan></text></g></g></svg>
diff --git a/docs/api/config.rst b/docs/api/config.rst
index ae913d32c..ab3ff0fe1 100644
--- a/docs/api/config.rst
+++ b/docs/api/config.rst
@@ -35,6 +35,7 @@
.. automethod:: set_authentication_policy
.. automethod:: set_authorization_policy
+ .. automethod:: set_default_csrf_options
.. automethod:: set_default_permission
.. automethod:: add_permission
@@ -65,7 +66,9 @@
.. automethod:: add_traverser
.. automethod:: add_tween
.. automethod:: add_route_predicate
+ .. automethod:: add_subscriber_predicate
.. automethod:: add_view_predicate
+ .. automethod:: add_view_deriver
.. automethod:: set_request_factory
.. automethod:: set_root_factory
.. automethod:: set_session_factory
diff --git a/docs/api/events.rst b/docs/api/events.rst
index 31a0e22c1..0a8463740 100644
--- a/docs/api/events.rst
+++ b/docs/api/events.rst
@@ -21,6 +21,8 @@ Event Types
.. autoclass:: ContextFound
+.. autoclass:: BeforeTraversal
+
.. autoclass:: NewResponse
.. autoclass:: BeforeRender
diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst
index faca0fbb6..cb411458d 100644
--- a/docs/api/exceptions.rst
+++ b/docs/api/exceptions.rst
@@ -5,6 +5,8 @@
.. automodule:: pyramid.exceptions
+ .. autoexception:: BadCSRFOrigin
+
.. autoexception:: BadCSRFToken
.. autoexception:: PredicateMismatch
diff --git a/docs/api/index.rst b/docs/api/index.rst
index cb38aa0b2..4b912e2bd 100644
--- a/docs/api/index.rst
+++ b/docs/api/index.rst
@@ -1,4 +1,4 @@
-.. _html_api_documentation:
+.. _api_documentation:
API Documentation
=================
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index de2a664a4..521d65d2b 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -17,6 +17,9 @@ Event-Related Interfaces
.. autointerface:: IContextFound
:members:
+ .. autointerface:: IBeforeTraversal
+ :members:
+
.. autointerface:: INewResponse
:members:
@@ -56,6 +59,9 @@ Other Interfaces
.. autointerface:: IRenderer
:members:
+ .. autointerface:: IRequestFactory
+ :members:
+
.. autointerface:: IResponseFactory
:members:
@@ -91,3 +97,9 @@ Other Interfaces
.. autointerface:: ICacheBuster
:members:
+
+ .. autointerface:: IViewDeriver
+ :members:
+
+ .. autointerface:: IViewDeriverInfo
+ :members:
diff --git a/docs/api/paster.rst b/docs/api/paster.rst
index edc3738fc..27bc81a1f 100644
--- a/docs/api/paster.rst
+++ b/docs/api/paster.rst
@@ -11,4 +11,4 @@
.. autofunction:: get_appsettings(config_uri, name=None, options=None)
- .. autofunction:: setup_logging(config_uri)
+ .. autofunction:: setup_logging(config_uri, global_conf=None)
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 105ffb5a7..52bf50078 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -13,7 +13,8 @@
current_route_path, static_url, static_path,
model_url, resource_url, resource_path, set_property,
effective_principals, authenticated_userid,
- unauthenticated_userid, has_permission
+ unauthenticated_userid, has_permission,
+ invoke_exception_view
.. attribute:: context
@@ -259,6 +260,8 @@
See also :ref:`subrequest_chapter`.
+ .. automethod:: invoke_exception_view
+
.. automethod:: has_permission
.. automethod:: add_response_callback
diff --git a/docs/api/session.rst b/docs/api/session.rst
index 474e2bb32..56c4f52d7 100644
--- a/docs/api/session.rst
+++ b/docs/api/session.rst
@@ -9,6 +9,8 @@
.. autofunction:: signed_deserialize
+ .. autofunction:: check_csrf_origin
+
.. autofunction:: check_csrf_token
.. autofunction:: SignedCookieSessionFactory
diff --git a/docs/api/viewderivers.rst b/docs/api/viewderivers.rst
new file mode 100644
index 000000000..2a141501e
--- /dev/null
+++ b/docs/api/viewderivers.rst
@@ -0,0 +1,17 @@
+.. _viewderivers_module:
+
+:mod:`pyramid.viewderivers`
+---------------------------
+
+.. automodule:: pyramid.viewderivers
+
+ .. attribute:: INGRESS
+
+ Constant representing the request ingress, for use in ``under``
+ arguments to :meth:`pyramid.config.Configurator.add_view_deriver`.
+
+ .. attribute:: VIEW
+
+ Constant representing the :term:`view callable` at the end of the view
+ pipeline, for use in ``over`` arguments to
+ :meth:`pyramid.config.Configurator.add_view_deriver`.
diff --git a/docs/authorintro.rst b/docs/authorintro.rst
index ebc6bcff8..6e96fad9a 100644
--- a/docs/authorintro.rst
+++ b/docs/authorintro.rst
@@ -54,7 +54,14 @@ technologies.
Book Content
============
-This book is divided into three major parts:
+This book is divided into four major parts:
+
+:ref:`tutorials`
+
+ Each tutorial builds a sample application or implements a set of
+ concepts with a sample; it then describes the application or
+ concepts in terms of the sample. You should read the tutorials if
+ you want a guided tour of :app:`Pyramid`.
:ref:`narrative_documentation`
@@ -66,19 +73,16 @@ This book is divided into three major parts:
out-of-order, or when you need only a reminder about a particular
topic while you're developing an application.
-:ref:`tutorials`
-
- Each tutorial builds a sample application or implements a set of
- concepts with a sample; it then describes the application or
- concepts in terms of the sample. You should read the tutorials if
- you want a guided tour of :app:`Pyramid`.
-
:ref:`api_documentation`
Comprehensive reference material for every public API exposed by
:app:`Pyramid`. The API documentation is organized
alphabetically by module name.
+:ref:`pscripts_documentation`
+
+ ``p*`` scripts included with :app:`Pyramid`.
+
.. index::
single: repoze.zope2
single: Zope 3
diff --git a/docs/conf.py b/docs/conf.py
index 8a9bac6ed..518f7e784 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -53,21 +53,24 @@ extensions = [
'sphinx.ext.doctest',
'repoze.sphinx.autointerface',
'sphinx.ext.viewcode',
- 'sphinx.ext.intersphinx'
+ 'sphinx.ext.intersphinx',
+ 'sphinxcontrib.programoutput',
+ # enable pylons_sphinx_latesturl when this branch is no longer "latest"
+ # 'pylons_sphinx_latesturl',
]
# Looks for objects in external projects
intersphinx_mapping = {
- 'colander': ( 'http://docs.pylonsproject.org/projects/colander/en/latest', None),
+ 'colander': ('http://docs.pylonsproject.org/projects/colander/en/latest', None),
'cookbook': ('http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None),
'deform': ('http://docs.pylonsproject.org/projects/deform/en/latest', None),
'jinja2': ('http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None),
'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None),
- 'python': ('http://docs.python.org', None),
- 'python3': ('http://docs.python.org/3', None),
+ 'python': ('https://docs.python.org/3', None),
+ 'pytest': ('http://pytest.org/latest/', None),
'sqla': ('http://docs.sqlalchemy.org/en/latest', None),
- 'tm': ('http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/', None),
- 'toolbar': ('http://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None),
+ 'tm': ('http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/', None),
+ 'toolbar': ('http://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None),
'tstring': ('http://docs.pylonsproject.org/projects/translationstring/en/latest', None),
'tutorials': ('http://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None),
'venusian': ('http://docs.pylonsproject.org/projects/venusian/en/latest', None),
@@ -123,12 +126,33 @@ if book:
# Options for HTML output
# -----------------------
+# enable pylons_sphinx_latesturl when this branch is no longer "latest"
+# pylons_sphinx_latesturl_base = (
+# 'http://docs.pylonsproject.org/projects/pyramid/en/latest/')
+# pylons_sphinx_latesturl_pagename_overrides = {
+# # map old pagename -> new pagename
+# 'whatsnew-1.0': 'index',
+# 'whatsnew-1.1': 'index',
+# 'whatsnew-1.2': 'index',
+# 'whatsnew-1.3': 'index',
+# 'whatsnew-1.4': 'index',
+# 'whatsnew-1.5': 'index',
+# 'whatsnew-1.6': 'index',
+# 'whatsnew-1.7': 'index',
+# 'tutorials/gae/index': 'index',
+# 'api/chameleon_text': 'api',
+# 'api/chameleon_zpt': 'api',
+# }
html_theme = 'pyramid'
html_theme_path = pylons_sphinx_themes.get_html_themes_path()
html_theme_options = dict(
github_url='https://github.com/Pylons/pyramid',
+ # On master branch and new branch still in
+ # pre-release status: true; else: false.
in_progress='true',
+ # On branches previous to "latest": true; else: false.
+ outdated='false',
)
# The name for this set of Sphinx documents. If None, it defaults to
diff --git a/docs/conventions.rst b/docs/conventions.rst
index a9d2550bf..de041da04 100644
--- a/docs/conventions.rst
+++ b/docs/conventions.rst
@@ -35,7 +35,7 @@ References to glossary terms are presented using the following style:
URLs are presented using the following style:
- `Pylons <http://pylonsproject.org>`_
+ `Pylons <http://www.pylonsproject.org>`_
References to sections and chapters are presented using the following
style:
@@ -53,46 +53,39 @@ Code and configuration file blocks are presented in the following style:
Example blocks representing UNIX shell commands are prefixed with a ``$``
character, e.g.:
- .. code-block:: text
+ .. code-block:: bash
- $ $VENV/bin/nosetests
+ $ $VENV/bin/py.test -q
-(See :term:`virtualenv` for the meaning of ``$VENV``)
+See :term:`venv` for the meaning of ``$VENV``.
-Example blocks representing Windows ``cmd.exe`` commands are prefixed with a
-drive letter and/or a directory name, e.g.:
+Example blocks representing Windows commands are prefixed with a drive letter
+with an optional directory name, e.g.:
- .. code-block:: text
+ .. code-block:: doscon
- c:\examples> %VENV%\Scripts\nosetests
+ c:\examples> %VENV%\Scripts\py.test -q
-(See :term:`virtualenv` for the meaning of ``%VENV%``)
-
-Sometimes, when it's unknown which directory is current, Windows ``cmd.exe``
-example block commands are prefixed only with a ``>`` character, e.g.:
-
- .. code-block:: text
-
- > %VENV%\Scripts\nosetests
+See :term:`venv` for the meaning of ``%VENV%``.
When a command that should be typed on one line is too long to fit on a page,
-the backslash ``\`` is used to indicate that the following printed line
-should actually be part of the command:
+the backslash ``\`` is used to indicate that the following printed line should
+be part of the command:
- .. code-block:: text
+ .. code-block:: bash
- c:\bigfntut\tutorial> %VENV%\Scripts\nosetests --cover-package=tutorial \
- --cover-erase --with-coverage
+ $VENV/bin/py.test tutorial/tests.py --cov-report term-missing \
+ --cov=tutorial -q
-A sidebar, which presents a concept tangentially related to content
-discussed on a page, is rendered like so:
+A sidebar, which presents a concept tangentially related to content discussed
+on a page, is rendered like so:
.. sidebar:: This is a sidebar
Sidebar information.
-When multiple objects are imported from the same package,
-the following convention is used:
+When multiple objects are imported from the same package, the following
+convention is used:
.. code-block:: python
@@ -103,9 +96,9 @@ the following convention is used:
It may look unusual, but it has advantages:
-* It allows one to swap out the higher-level package ``foo`` for something
- else that provides the similar API. An example would be swapping out
- one database for another (e.g., graduating from SQLite to PostgreSQL).
+* It allows one to swap out the higher-level package ``foo`` for something else
+ that provides the similar API. An example would be swapping out one database
+ for another (e.g., graduating from SQLite to PostgreSQL).
* Looks more neat in cases where a large number of objects get imported from
that package.
diff --git a/docs/copyright.rst b/docs/copyright.rst
index 3beaee7f7..30ae40603 100644
--- a/docs/copyright.rst
+++ b/docs/copyright.rst
@@ -1,7 +1,7 @@
Copyright, Trademarks, and Attributions
=======================================
-*The Pyramid Web Framework, Version 1.1*
+"The Pyramid Web Framework, Version |version|"
by Chris McDonough
@@ -63,7 +63,7 @@ Contributors:
GitHub.
Cover Designer:
- Hugues Laflamme of `Kemeneur <http://www.kemeneur.com/>`_.
+ Hugues Laflamme of Kemeneur.
Used with permission:
@@ -80,8 +80,8 @@ Print Production
----------------
The print version of this book was produced using the `Sphinx
-<http://sphinx.pocoo.org/>`_ documentation generation system and the
-`LaTeX <http://www.latex-project.org/>`_ typesetting system.
+<http://www.sphinx-doc.org/en/stable/>`_ documentation generation system and
+the `LaTeX <http://www.latex-project.org/>`_ typesetting system.
Contacting The Publisher
------------------------
@@ -90,7 +90,7 @@ Please send documentation licensing inquiries, translation inquiries,
and other business communications to `Agendaless Consulting
<mailto:webmaster@agendaless.com>`_. Please send software and other
technical queries to the `Pylons-devel mailing list
-<http://groups.google.com/group/pylons-devel>`_.
+<https://groups.google.com/forum/#!forum/pylons-devel>`_.
HTML Version and Source Code
----------------------------
@@ -101,4 +101,3 @@ http://docs.pylonsproject.org/projects/pyramid/en/latest/
The source code for the examples used in this book are available
within the :app:`Pyramid` software distribution, always available
via https://github.com/Pylons/pyramid
-
diff --git a/docs/designdefense.rst b/docs/designdefense.rst
index ee6d5a317..5f65671bb 100644
--- a/docs/designdefense.rst
+++ b/docs/designdefense.rst
@@ -7,98 +7,94 @@ From time to time, challenges to various aspects of :app:`Pyramid` design are
lodged. To give context to discussions that follow, we detail some of the
design decisions and trade-offs here. In some cases, we acknowledge that the
framework can be made better and we describe future steps which will be taken
-to improve it; in some cases we just file the challenge as noted, as
-obviously you can't please everyone all of the time.
+to improve it. In others we just file the challenge as noted, as obviously you
+can't please everyone all of the time.
Pyramid Provides More Than One Way to Do It
-------------------------------------------
A canon of Python popular culture is "TIOOWTDI" ("there is only one way to do
-it", a slighting, tongue-in-cheek reference to Perl's "TIMTOWTDI", which is
-an acronym for "there is more than one way to do it").
-
-:app:`Pyramid` is, for better or worse, a "TIMTOWTDI" system. For example,
-it includes more than one way to resolve a URL to a :term:`view callable`:
-via :term:`url dispatch` or :term:`traversal`. Multiple methods of
-configuration exist: :term:`imperative configuration`, :term:`configuration
-decoration`, and :term:`ZCML` (optionally via :term:`pyramid_zcml`). It works
-with multiple different kinds of persistence and templating systems. And so
-on. However, the existence of most of these overlapping ways to do things
-are not without reason and purpose: we have a number of audiences to serve,
-and we believe that TIMTOWTI at the web framework level actually *prevents* a
-much more insidious and harmful set of duplication at higher levels in the
-Python web community.
-
-:app:`Pyramid` began its life as :mod:`repoze.bfg`, written by a team of
-people with many years of prior :term:`Zope` experience. The idea of
+it", a slighting, tongue-in-cheek reference to Perl's "TIMTOWTDI", which is an
+acronym for "there is more than one way to do it").
+
+:app:`Pyramid` is, for better or worse, a "TIMTOWTDI" system. For example, it
+includes more than one way to resolve a URL to a :term:`view callable`: via
+:term:`url dispatch` or :term:`traversal`. Multiple methods of configuration
+exist: :term:`imperative configuration`, :term:`configuration decoration`, and
+:term:`ZCML` (optionally via :term:`pyramid_zcml`). It works with multiple
+different kinds of persistence and templating systems. And so on. However, the
+existence of most of these overlapping ways to do things are not without reason
+and purpose: we have a number of audiences to serve, and we believe that
+TIMTOWTDI at the web framework level actually *prevents* a much more insidious
+and harmful set of duplication at higher levels in the Python web community.
+
+:app:`Pyramid` began its life as :mod:`repoze.bfg`, written by a team of people
+with many years of prior :term:`Zope` experience. The idea of
:term:`traversal` and the way :term:`view lookup` works was stolen entirely
from Zope. The authorization subsystem provided by :app:`Pyramid` is a
derivative of Zope's. The idea that an application can be *extended* without
forking is also a Zope derivative.
Implementations of these features were *required* to allow the :app:`Pyramid`
-authors to build the bread-and-butter CMS-type systems for customers in the
-way in which they were accustomed. No other system, save for Zope itself,
-had such features, and Zope itself was beginning to show signs of its age.
-We were becoming hampered by consequences of its early design mistakes.
-Zope's lack of documentation was also difficult to work around: it was hard
-to hire smart people to work on Zope applications, because there was no
-comprehensive documentation set to point them at which explained "it all" in
-one consumable place, and it was too large and self-inconsistent to document
-properly. Before :mod:`repoze.bfg` went under development, its authors
-obviously looked around for other frameworks that fit the bill. But no
-non-Zope framework did. So we embarked on building :mod:`repoze.bfg`.
+authors to build the bread-and-butter CMS-type systems for customers in the way
+in which they were accustomed. No other system, save for Zope itself, had such
+features, and Zope itself was beginning to show signs of its age. We were
+becoming hampered by consequences of its early design mistakes. Zope's lack of
+documentation was also difficult to work around. It was hard to hire smart
+people to work on Zope applications because there was no comprehensive
+documentation set which explained "it all" in one consumable place, and it was
+too large and self-inconsistent to document properly. Before :mod:`repoze.bfg`
+went under development, its authors obviously looked around for other
+frameworks that fit the bill. But no non-Zope framework did. So we embarked on
+building :mod:`repoze.bfg`.
As the result of our research, however, it became apparent that, despite the
-fact that no *one* framework had all the features we required, lots of
-existing frameworks had good, and sometimes very compelling ideas. In
-particular, :term:`URL dispatch` is a more direct mechanism to map URLs to
-code.
+fact that no *one* framework had all the features we required, lots of existing
+frameworks had good, and sometimes very compelling ideas. In particular,
+:term:`URL dispatch` is a more direct mechanism to map URLs to code.
So, although we couldn't find a framework, save for Zope, that fit our needs,
and while we incorporated a lot of Zope ideas into BFG, we also emulated the
features we found compelling in other frameworks (such as :term:`url
-dispatch`). After the initial public release of BFG, as time went on,
-features were added to support people allergic to various Zope-isms in the
-system, such as the ability to configure the application using
-:term:`imperative configuration` and :term:`configuration decoration` rather
-than solely using :term:`ZCML`, and the elimination of the required use of
-:term:`interface` objects. It soon became clear that we had a system that
-was very generic, and was beginning to appeal to non-Zope users as well as
-ex-Zope users.
+dispatch`). After the initial public release of BFG, as time went on, features
+were added to support people allergic to various Zope-isms in the system, such
+as the ability to configure the application using :term:`imperative
+configuration` and :term:`configuration decoration`, rather than solely using
+:term:`ZCML`, and the elimination of the required use of :term:`interface`
+objects. It soon became clear that we had a system that was very generic, and
+was beginning to appeal to non-Zope users as well as ex-Zope users.
As the result of this generalization, it became obvious BFG shared 90% of its
-featureset with the featureset of Pylons 1, and thus had a very similar
-target market. Because they were so similar, choosing between the two
-systems was an exercise in frustration for an otherwise non-partisan
-developer. It was also strange for the Pylons and BFG development
-communities to be in competition for the same set of users, given how similar
-the two frameworks were. So the Pylons and BFG teams began to work together
-to form a plan to merge. The features missing from BFG (notably :term:`view
-handler` classes, flash messaging, and other minor missing bits), were added,
-to provide familiarity to ex-Pylons users. The result is :app:`Pyramid`.
-
-The Python web framework space is currently notoriously balkanized. We're
-truly hoping that the amalgamation of components in :app:`Pyramid` will
-appeal to at least two currently very distinct sets of users: Pylons and BFG
-users. By unifying the best concepts from Pylons and BFG into a single
-codebase and leaving the bad concepts from their ancestors behind, we'll be
-able to consolidate our efforts better, share more code, and promote our
-efforts as a unit rather than competing pointlessly. We hope to be able to
-shortcut the pack mentality which results in a *much larger* duplication of
-effort, represented by competing but incredibly similar applications and
-libraries, each built upon a specific low level stack that is incompatible
-with the other. We'll also shrink the choice of credible Python web
-frameworks down by at least one. We're also hoping to attract users from
-other communities (such as Zope's and TurboGears') by providing the features
-they require, while allowing enough flexibility to do things in a familiar
-fashion. Some overlap of functionality to achieve these goals is expected
-and unavoidable, at least if we aim to prevent pointless duplication at
-higher levels. If we've done our job well enough, the various audiences will
-be able to coexist and cooperate rather than firing at each other across some
-imaginary web framework DMZ.
-
-Pyramid Uses A Zope Component Architecture ("ZCA") Registry
+feature set with the feature set of Pylons 1, and thus had a very similar
+target market. Because they were so similar, choosing between the two systems
+was an exercise in frustration for an otherwise non-partisan developer. It was
+also strange for the Pylons and BFG development communities to be in
+competition for the same set of users, given how similar the two frameworks
+were. So the Pylons and BFG teams began to work together to form a plan to
+merge. The features missing from BFG (notably :term:`view handler` classes,
+flash messaging, and other minor missing bits), were added to provide
+familiarity to ex-Pylons users. The result is :app:`Pyramid`.
+
+The Python web framework space is currently notoriously balkanized. We're truly
+hoping that the amalgamation of components in :app:`Pyramid` will appeal to at
+least two currently very distinct sets of users: Pylons and BFG users. By
+unifying the best concepts from Pylons and BFG into a single codebase, and
+leaving the bad concepts from their ancestors behind, we'll be able to
+consolidate our efforts better, share more code, and promote our efforts as a
+unit rather than competing pointlessly. We hope to be able to shortcut the pack
+mentality which results in a *much larger* duplication of effort, represented
+by competing but incredibly similar applications and libraries, each built upon
+a specific low level stack that is incompatible with the other. We'll also
+shrink the choice of credible Python web frameworks down by at least one. We're
+also hoping to attract users from other communities (such as Zope's and
+TurboGears') by providing the features they require, while allowing enough
+flexibility to do things in a familiar fashion. Some overlap of functionality
+to achieve these goals is expected and unavoidable, at least if we aim to
+prevent pointless duplication at higher levels. If we've done our job well
+enough, the various audiences will be able to coexist and cooperate rather than
+firing at each other across some imaginary web framework DMZ.
+
+Pyramid Uses a Zope Component Architecture ("ZCA") Registry
-----------------------------------------------------------
:app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) "component
@@ -135,7 +131,7 @@ obvious.
First, what's a "utility"? Well, for the purposes of this discussion, and
for the purpose of the code above, it's just not very important. If you
really want to know, you can read `this
-<http://www.muthukadan.net/docs/zca.html#utility>`_. However, still, readers
+<http://muthukadan.net/docs/zca.html#utility>`_. However, still, readers
of such code need to understand the concept in order to parse it. This is
problem number one.
@@ -146,7 +142,7 @@ dictionary API, but that's not very important in this context. That's
problem number two.
Third of all, what does the ``getUtility`` function do? It's performing a
-lookup for the ``ISettings`` "utility" that should return.. well, a utility.
+lookup for the ``ISettings`` "utility" that should return... well, a utility.
Note how we've already built up a dependency on the understanding of an
:term:`interface` and the concept of "utility" to answer this question: a bad
sign so far. Note also that the answer is circular, a *really* bad sign.
@@ -156,12 +152,12 @@ registry" of course. What's a component registry? Problem number four.
Fifth, assuming you buy that there's some magical registry hanging around,
where *is* this registry? *Homina homina*... "around"? That's sort of the
-best answer in this context (a more specific answer would require knowledge
-of internals). Can there be more than one registry? Yes. So *which*
-registry does it find the registration in? Well, the "current" registry of
-course. In terms of :app:`Pyramid`, the current registry is a thread local
-variable. Using an API that consults a thread local makes understanding how
-it works non-local.
+best answer in this context (a more specific answer would require knowledge of
+internals). Can there be more than one registry? Yes. So in *which* registry
+does it find the registration? Well, the "current" registry of course. In
+terms of :app:`Pyramid`, the current registry is a thread local variable.
+Using an API that consults a thread local makes understanding how it works
+non-local.
You've now bought in to the fact that there's a registry that is just hanging
around. But how does the registry get populated? Why, via code that calls
@@ -170,20 +166,20 @@ registration of ``ISettings`` is made by the framework itself under the hood:
it's not present in any user configuration. This is extremely hard to
comprehend. Problem number six.
-Clearly there's some amount of cognitive load here that needs to be borne by
-a reader of code that extends the :app:`Pyramid` framework due to its use of
-the ZCA, even if he or she is already an expert Python programmer and whom is
-an expert in the domain of web applications. This is suboptimal.
+Clearly there's some amount of cognitive load here that needs to be borne by a
+reader of code that extends the :app:`Pyramid` framework due to its use of the
+ZCA, even if they are already an expert Python programmer and an expert in the
+domain of web applications. This is suboptimal.
Ameliorations
+++++++++++++
First, the primary amelioration: :app:`Pyramid` *does not expect application
-developers to understand ZCA concepts or any of its APIs*. If an
-*application* developer needs to understand a ZCA concept or API during the
-creation of a :app:`Pyramid` application, we've failed on some axis.
+developers to understand ZCA concepts or any of its APIs*. If an *application*
+developer needs to understand a ZCA concept or API during the creation of a
+:app:`Pyramid` application, we've failed on some axis.
-Instead, the framework hides the presence of the ZCA registry behind
+Instead the framework hides the presence of the ZCA registry behind
special-purpose API functions that *do* use ZCA APIs. Take for example the
``pyramid.security.authenticated_userid`` function, which returns the userid
present in the current request or ``None`` if no userid is present in the
@@ -195,10 +191,9 @@ current request. The application developer calls it like so:
from pyramid.security import authenticated_userid
userid = authenticated_userid(request)
-He now has the current user id.
+They now have the current user id.
-Under its hood however, the implementation of ``authenticated_userid``
-is this:
+Under its hood however, the implementation of ``authenticated_userid`` is this:
.. code-block:: python
:linenos:
@@ -215,58 +210,56 @@ is this:
return policy.authenticated_userid(request)
Using such wrappers, we strive to always hide the ZCA API from application
-developers. Application developers should just never know about the ZCA API:
-they should call a Python function with some object germane to the domain as
-an argument, and it should return a result. A corollary that follows is
-that any reader of an application that has been written using :app:`Pyramid`
-needn't understand the ZCA API either.
+developers. Application developers should just never know about the ZCA API;
+they should call a Python function with some object germane to the domain as an
+argument, and it should return a result. A corollary that follows is that any
+reader of an application that has been written using :app:`Pyramid` needn't
+understand the ZCA API either.
Hiding the ZCA API from application developers and code readers is a form of
enhancing domain specificity. No application developer wants to need to
-understand the small, detailed mechanics of how a web framework does its
-thing. People want to deal in concepts that are closer to the domain they're
-working in: for example, web developers want to know about *users*, not
-*utilities*. :app:`Pyramid` uses the ZCA as an implementation detail, not as
-a feature which is exposed to end users.
+understand the small, detailed mechanics of how a web framework does its thing.
+People want to deal in concepts that are closer to the domain they're working
+in. For example, web developers want to know about *users*, not *utilities*.
+:app:`Pyramid` uses the ZCA as an implementation detail, not as a feature which
+is exposed to end users.
However, unlike application developers, *framework developers*, including
people who want to override :app:`Pyramid` functionality via preordained
-framework plugpoints like traversal or view lookup *must* understand the ZCA
+framework plugpoints like traversal or view lookup, *must* understand the ZCA
registry API.
:app:`Pyramid` framework developers were so concerned about conceptual load
-issues of the ZCA registry API for framework developers that a `replacement
-registry implementation <https://github.com/repoze/repoze.component>`_
-named :mod:`repoze.component` was actually developed. Though this package
-has a registry implementation which is fully functional and well-tested, and
-its API is much nicer than the ZCA registry API, work on it was largely
-abandoned and it is not used in :app:`Pyramid`. We continued to use a ZCA
-registry within :app:`Pyramid` because it ultimately proved a better fit.
+issues of the ZCA registry API that a `replacement registry implementation
+<https://github.com/repoze/repoze.component>`_ named :mod:`repoze.component`
+was actually developed. Though this package has a registry implementation
+which is fully functional and well-tested, and its API is much nicer than the
+ZCA registry API, work on it was largely abandoned, and it is not used in
+:app:`Pyramid`. We continued to use a ZCA registry within :app:`Pyramid`
+because it ultimately proved a better fit.
.. note::
- We continued using ZCA registry rather than disusing it in
- favor of using the registry implementation in
- :mod:`repoze.component` largely because the ZCA concept of
- interfaces provides for use of an interface hierarchy, which is
- useful in a lot of scenarios (such as context type inheritance).
- Coming up with a marker type that was something like an interface
- that allowed for this functionality seemed like it was just
- reinventing the wheel.
-
-Making framework developers and extenders understand the ZCA registry API is
-a trade-off. We (the :app:`Pyramid` developers) like the features that the
-ZCA registry gives us, and we have long-ago borne the weight of understanding
-what it does and how it works. The authors of :app:`Pyramid` understand the
-ZCA deeply and can read code that uses it as easily as any other code.
+ We continued using ZCA registry rather than disusing it in favor of using
+ the registry implementation in :mod:`repoze.component` largely because the
+ ZCA concept of interfaces provides for use of an interface hierarchy, which
+ is useful in a lot of scenarios (such as context type inheritance). Coming
+ up with a marker type that was something like an interface that allowed for
+ this functionality seemed like it was just reinventing the wheel.
+
+Making framework developers and extenders understand the ZCA registry API is a
+trade-off. We (the :app:`Pyramid` developers) like the features that the ZCA
+registry gives us, and we have long-ago borne the weight of understanding what
+it does and how it works. The authors of :app:`Pyramid` understand the ZCA
+deeply and can read code that uses it as easily as any other code.
But we recognize that developers who might want to extend the framework are not
-as comfortable with the ZCA registry API as the original developers are with
-it. So, for the purposes of being kind to third-party :app:`Pyramid`
-framework developers in, we've drawn some lines in the sand.
+as comfortable with the ZCA registry API as the original developers. So for
+the purpose of being kind to third-party :app:`Pyramid` framework developers,
+we've drawn some lines in the sand.
-In all core code, We've made use of ZCA global API functions such as
-``zope.component.getUtility`` and ``zope.component.getAdapter`` the exception
+In all core code, we've made use of ZCA global API functions, such as
+``zope.component.getUtility`` and ``zope.component.getAdapter``, the exception
instead of the rule. So instead of:
.. code-block:: python
@@ -286,9 +279,9 @@ instead of the rule. So instead of:
registry = get_current_registry()
policy = registry.getUtility(IAuthenticationPolicy)
-While the latter is more verbose, it also arguably makes it more obvious
-what's going on. All of the :app:`Pyramid` core code uses this pattern
-rather than the ZCA global API.
+While the latter is more verbose, it also arguably makes it more obvious what's
+going on. All of the :app:`Pyramid` core code uses this pattern rather than
+the ZCA global API.
Rationale
+++++++++
@@ -313,13 +306,12 @@ the ZCA registry:
view that is only found when the context is some class of object, or when
the context implements some :term:`interface`.
-- Singularity. There's only one place where "application configuration"
- lives in a :app:`Pyramid` application: in a component registry. The
- component registry answers questions made to it by the framework at runtime
- based on the configuration of *an application*. Note: "an application" is
- not the same as "a process", multiple independently configured copies of
- the same :app:`Pyramid` application are capable of running in the same
- process space.
+- Singularity. There's only one place where "application configuration" lives
+ in a :app:`Pyramid` application: in a component registry. The component
+ registry answers questions made to it by the framework at runtime based on
+ the configuration of *an application*. Note: "an application" is not the
+ same as "a process"; multiple independently configured copies of the same
+ :app:`Pyramid` application are capable of running in the same process space.
- Composability. A ZCA component registry can be populated imperatively, or
there's an existing mechanism to populate a registry via the use of a
@@ -337,10 +329,9 @@ the ZCA registry:
(non-Zope) frameworks.
- Testability. Judicious use of the ZCA registry in framework code makes
- testing that code slightly easier. Instead of using monkeypatching or
- other facilities to register mock objects for testing, we inject
- dependencies via ZCA registrations and then use lookups in the code find
- our mock objects.
+ testing that code slightly easier. Instead of using monkeypatching or other
+ facilities to register mock objects for testing, we inject dependencies via
+ ZCA registrations, then use lookups in the code to find our mock objects.
- Speed. The ZCA registry is very fast for a specific set of complex lookup
scenarios that :app:`Pyramid` uses, having been optimized through the years
@@ -354,17 +345,17 @@ Conclusion
++++++++++
If you only *develop applications* using :app:`Pyramid`, there's not much to
-complain about here. You just should never need to understand the ZCA
-registry API: use documented :app:`Pyramid` APIs instead. However, you may
-be an application developer who doesn't read API documentation because it's
-unmanly. Instead you read the raw source code, and because you haven't read
-the documentation, you don't know what functions, classes, and methods even
-*form* the :app:`Pyramid` API. As a result, you've now written code that
-uses internals and you've painted yourself into a conceptual corner as a
-result of needing to wrestle with some ZCA-using implementation detail. If
-this is you, it's extremely hard to have a lot of sympathy for you. You'll
-either need to get familiar with how we're using the ZCA registry or you'll
-need to use only the documented APIs; that's why we document them as APIs.
+complain about here. You just should never need to understand the ZCA registry
+API; use documented :app:`Pyramid` APIs instead. However, you may be an
+application developer who doesn't read API documentation. Instead you
+read the raw source code, and because you haven't read the API documentation,
+you don't know what functions, classes, and methods even *form* the
+:app:`Pyramid` API. As a result, you've now written code that uses internals,
+and you've painted yourself into a conceptual corner, needing to wrestle with
+some ZCA-using implementation detail. If this is you, it's extremely hard to
+have a lot of sympathy for you. You'll either need to get familiar with how
+we're using the ZCA registry or you'll need to use only the documented APIs;
+that's why we document them as APIs.
If you *extend* or *develop* :app:`Pyramid` (create new directives, use some
of the more obscure hooks as described in :ref:`hooks_chapter`, or work on
@@ -373,6 +364,7 @@ at least some ZCA concepts. In some places it's used unabashedly, and will
be forever. We know it's quirky, but it's also useful and fundamentally
understandable if you take the time to do some reading about it.
+
.. _zcml_encouragement:
Pyramid "Encourages Use of ZCML"
@@ -388,15 +380,16 @@ completely optional. No ZCML is required at all to use :app:`Pyramid`, nor
any other sort of frameworky declarative frontend to application
configuration.
-Pyramid Does Traversal, And I Don't Like Traversal
+
+Pyramid Does Traversal, and I Don't Like Traversal
--------------------------------------------------
In :app:`Pyramid`, :term:`traversal` is the act of resolving a URL path to a
-:term:`resource` object in a resource tree. Some people are uncomfortable
-with this notion, and believe it is wrong. Thankfully, if you use
-:app:`Pyramid`, and you don't want to model your application in terms of a
-resource tree, you needn't use it at all. Instead, use :term:`URL dispatch`
-to map URL paths to views.
+:term:`resource` object in a resource tree. Some people are uncomfortable with
+this notion, and believe it is wrong. Thankfully if you use :app:`Pyramid` and
+you don't want to model your application in terms of a resource tree, you
+needn't use it at all. Instead use :term:`URL dispatch` to map URL paths to
+views.
The idea that some folks believe traversal is unilaterally wrong is
understandable. The people who believe it is wrong almost invariably have
@@ -431,7 +424,8 @@ URL pattern matching.
But the point is ultimately moot. If you don't want to use traversal, you
needn't. Use URL dispatch instead.
-Pyramid Does URL Dispatch, And I Don't Like URL Dispatch
+
+Pyramid Does URL Dispatch, and I Don't Like URL Dispatch
--------------------------------------------------------
In :app:`Pyramid`, :term:`url dispatch` is the act of resolving a URL path to
@@ -453,41 +447,40 @@ I'll argue that URL dispatch is ultimately useful, even if you want to use
traversal as well. You can actually *combine* URL dispatch and traversal in
:app:`Pyramid` (see :ref:`hybrid_chapter`). One example of such a usage: if
you want to emulate something like Zope 2's "Zope Management Interface" UI on
-top of your object graph (or any administrative interface), you can register
-a route like ``config.add_route('manage', '/manage/*traverse')`` and then
-associate "management" views in your code by using the ``route_name``
-argument to a ``view`` configuration,
-e.g. ``config.add_view('.some.callable', context=".some.Resource",
-route_name='manage')``. If you wire things up this way someone then walks up
-to for example, ``/manage/ob1/ob2``, they might be presented with a
-management interface, but walking up to ``/ob1/ob2`` would present them with
-the default object view. There are other tricks you can pull in these hybrid
-configurations if you're clever (and maybe masochistic) too.
-
-Also, if you are a URL dispatch hater, if you should ever be asked to write
-an application that must use some legacy relational database structure, you
-might find that using URL dispatch comes in handy for one-off associations
-between views and URL paths. Sometimes it's just pointless to add a node to
-the object graph that effectively represents the entry point for some bit of
-code. You can just use a route and be done with it. If a route matches, a
-view associated with the route will be called; if no route matches,
-:app:`Pyramid` falls back to using traversal.
+top of your object graph (or any administrative interface), you can register a
+route like ``config.add_route('manage', '/manage/*traverse')`` and then
+associate "management" views in your code by using the ``route_name`` argument
+to a ``view`` configuration, e.g., ``config.add_view('.some.callable',
+context=".some.Resource", route_name='manage')``. If you wire things up this
+way, someone then walks up to, for example, ``/manage/ob1/ob2``, they might be
+presented with a management interface, but walking up to ``/ob1/ob2`` would
+present them with the default object view. There are other tricks you can pull
+in these hybrid configurations if you're clever (and maybe masochistic) too.
+
+Also, if you are a URL dispatch hater, if you should ever be asked to write an
+application that must use some legacy relational database structure, you might
+find that using URL dispatch comes in handy for one-off associations between
+views and URL paths. Sometimes it's just pointless to add a node to the object
+graph that effectively represents the entry point for some bit of code. You
+can just use a route and be done with it. If a route matches, a view
+associated with the route will be called. If no route matches, :app:`Pyramid`
+falls back to using traversal.
But the point is ultimately moot. If you use :app:`Pyramid`, and you really
don't want to use URL dispatch, you needn't use it at all. Instead, use
:term:`traversal` exclusively to map URL paths to views, just like you do in
:term:`Zope`.
+
Pyramid Views Do Not Accept Arbitrary Keyword Arguments
-------------------------------------------------------
Many web frameworks (Zope, TurboGears, Pylons 1.X, Django) allow for their
variant of a :term:`view callable` to accept arbitrary keyword or positional
-arguments, which are filled in using values present in the ``request.POST``
-or ``request.GET`` dictionaries or by values present in the route match
-dictionary. For example, a Django view will accept positional arguments
-which match information in an associated "urlconf" such as
-``r'^polls/(?P<poll_id>\d+)/$``:
+arguments, which are filled in using values present in the ``request.POST``,
+``request.GET``, or route match dictionaries. For example, a Django view will
+accept positional arguments which match information in an associated "urlconf"
+such as ``r'^polls/(?P<poll_id>\d+)/$``:
.. code-block:: python
:linenos:
@@ -495,8 +488,8 @@ which match information in an associated "urlconf" such as
def aview(request, poll_id):
return HttpResponse(poll_id)
-Zope, likewise allows you to add arbitrary keyword and positional
-arguments to any method of a resource object found via traversal:
+Zope likewise allows you to add arbitrary keyword and positional arguments to
+any method of a resource object found via traversal:
.. code-block:: python
:linenos:
@@ -513,13 +506,13 @@ match the names of the positional and keyword arguments in the request, and
the method is called (if possible) with its argument list filled with values
mentioned therein. TurboGears and Pylons 1.X operate similarly.
-Out of the box, :app:`Pyramid` is configured to have none of these features.
-By default, :app:`Pyramid` view callables always accept only ``request`` and
-no other arguments. The rationale: this argument specification matching done
-aggressively can be costly, and :app:`Pyramid` has performance as one of its
-main goals, so we've decided to make people, by default, obtain information
-by interrogating the request object within the view callable body instead of
-providing magic to do unpacking into the view argument list.
+Out of the box, :app:`Pyramid` is configured to have none of these features. By
+default :app:`Pyramid` view callables always accept only ``request`` and no
+other arguments. The rationale is, this argument specification matching when
+done aggressively can be costly, and :app:`Pyramid` has performance as one of
+its main goals. Therefore we've decided to make people, by default, obtain
+information by interrogating the request object within the view callable body
+instead of providing magic to do unpacking into the view argument list.
However, as of :app:`Pyramid` 1.0a9, user code can influence the way view
callables are expected to be called, making it possible to compose a system
@@ -557,7 +550,7 @@ you're building a simple bespoke web application:
sources using :meth:`pyramid.config.Configurator.include`.
- View and subscriber registrations made using :term:`interface` objects
- instead of class objects (e.g. :ref:`using_resource_interfaces`).
+ instead of class objects (e.g., :ref:`using_resource_interfaces`).
- A declarative :term:`authorization` system.
@@ -583,42 +576,41 @@ make unit testing and implementation substitutability easier.
In a bespoke web application, usually there's a single canonical deployment,
and therefore no possibility of multiple code forks. Extensibility is not
-required; the code is just changed in-place. Security requirements are often
-less granular. Using the features listed above will often be overkill for
-such an application.
+required; the code is just changed in place. Security requirements are often
+less granular. Using the features listed above will often be overkill for such
+an application.
If you don't like these features, it doesn't mean you can't or shouldn't use
-:app:`Pyramid`. They are all optional, and a lot of time has been spent
-making sure you don't need to know about them up-front. You can build
-"Pylons-1.X-style" applications using :app:`Pyramid` that are purely bespoke
-by ignoring the features above. You may find these features handy later
-after building a bespoke web application that suddenly becomes popular and
-requires extensibility because it must be deployed in multiple locations.
+:app:`Pyramid`. They are all optional, and a lot of time has been spent making
+sure you don't need to know about them up front. You can build "Pylons 1.X
+style" applications using :app:`Pyramid` that are purely bespoke by ignoring
+the features above. You may find these features handy later after building a
+bespoke web application that suddenly becomes popular and requires
+extensibility because it must be deployed in multiple locations.
Pyramid Is Too Big
------------------
-"The :app:`Pyramid` compressed tarball is larger than 2MB. It must be
-enormous!"
+"The :app:`Pyramid` compressed tarball is larger than 2MB. It must beenormous!"
-No. We just ship it with docs, test code, and scaffolding. Here's a
-breakdown of what's included in subdirectories of the package tree:
+No. We just ship it with docs, test code, and scaffolding. Here's a breakdown
+of what's included in subdirectories of the package tree:
docs/
- 4.9MB
+ 3.6MB
pyramid/tests/
- 2.0MB
+ 1.3MB
pyramid/scaffolds/
- 460KB
+ 133KB
-pyramid/ (except for ``pyramd/tests`` and ``pyramid/scaffolds``)
+pyramid/ (except for ``pyramid/tests`` and ``pyramid/scaffolds``)
- 844KB
+ 812KB
Of the approximately 34K lines of Python code in the package, the code
that actually has a chance of executing during normal operation, excluding
@@ -638,7 +630,8 @@ dependencies by forcing us to make better packaging decisions. Removing
Chameleon and Mako templating system dependencies in the Pyramid core in 1.5
let us shed most of the remainder of them.
-Pyramid "Cheats" To Obtain Speed
+
+Pyramid "Cheats" to Obtain Speed
--------------------------------
Complaints have been lodged by other web framework authors at various times
@@ -647,10 +640,11 @@ mechanism is our use (transitively) of the C extensions provided by
:mod:`zope.interface` to do fast lookups. Another claimed cheating mechanism
is the religious avoidance of extraneous function calls.
-If there's such a thing as cheating to get better performance, we want to
-cheat as much as possible. We optimize :app:`Pyramid` aggressively. This
-comes at a cost: the core code has sections that could be expressed more
-readably. As an amelioration, we've commented these sections liberally.
+If there's such a thing as cheating to get better performance, we want to cheat
+as much as possible. We optimize :app:`Pyramid` aggressively. This comes at a
+cost. The core code has sections that could be expressed with more readability.
+As an amelioration, we've commented these sections liberally.
+
Pyramid Gets Its Terminology Wrong ("MVC")
------------------------------------------
@@ -663,204 +657,199 @@ existing "MVC" framework uses its terminology. For example, you probably
expect that models are ORM models, controllers are classes that have methods
that map to URLs, and views are templates. :app:`Pyramid` indeed has each of
these concepts, and each probably *works* almost exactly like your existing
-"MVC" web framework. We just don't use the MVC terminology, as we can't
-square its usage in the web framework space with historical reality.
+"MVC" web framework. We just don't use the MVC terminology, as we can't square
+its usage in the web framework space with historical reality.
People very much want to give web applications the same properties as common
desktop GUI platforms by using similar terminology, and to provide some frame
of reference for how various components in the common web framework might
hang together. But in the opinion of the author, "MVC" doesn't match the web
very well in general. Quoting from the `Model-View-Controller Wikipedia entry
-<http://en.wikipedia.org/wiki/Model–view–controller>`_:
-
-.. code-block:: text
+<https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>`_:
- Though MVC comes in different flavors, control flow is generally as
- follows:
+ Though MVC comes in different flavors, control flow is generally as
+ follows:
- The user interacts with the user interface in some way (for
- example, presses a mouse button).
+ The user interacts with the user interface in some way (for example,
+ presses a mouse button).
- The controller handles the input event from the user interface,
- often via a registered handler or callback and converts the event
- into appropriate user action, understandable for the model.
+ The controller handles the input event from the user interface, often via
+ a registered handler or callback and converts the event into appropriate
+ user action, understandable for the model.
- The controller notifies the model of the user action, possibly
- resulting in a change in the model's state. (For example, the
- controller updates the user's shopping cart.)[5]
+ The controller notifies the model of the user action, possibly resulting
+ in a change in the model's state. (For example, the controller updates the
+ user's shopping cart.)[5]
- A view queries the model in order to generate an appropriate
- user interface (for example, the view lists the shopping cart's
- contents). Note that the view gets its own data from the model.
+ A view queries the model in order to generate an appropriate user
+ interface (for example, the view lists the shopping cart's contents). Note
+ that the view gets its own data from the model.
- The controller may (in some implementations) issue a general
- instruction to the view to render itself. In others, the view is
- automatically notified by the model of changes in state
- (Observer) which require a screen update.
+ The controller may (in some implementations) issue a general instruction
+ to the view to render itself. In others, the view is automatically
+ notified by the model of changes in state (Observer) which require a
+ screen update.
- The user interface waits for further user interactions, which
- restarts the cycle.
+ The user interface waits for further user interactions, which restarts the
+ cycle.
To the author, it seems as if someone edited this Wikipedia definition,
tortuously couching concepts in the most generic terms possible in order to
-account for the use of the term "MVC" by current web frameworks. I doubt
-such a broad definition would ever be agreed to by the original authors of
-the MVC pattern. But *even so*, it seems most MVC web frameworks fail to
-meet even this falsely generic definition.
+account for the use of the term "MVC" by current web frameworks. I doubt such
+a broad definition would ever be agreed to by the original authors of the MVC
+pattern. But *even so*, it seems most MVC web frameworks fail to meet even
+this falsely generic definition.
For example, do your templates (views) always query models directly as is
-claimed in "note that the view gets its own data from the model"? Probably
-not. My "controllers" tend to do this, massaging the data for easier use by
-the "view" (template). What do you do when your "controller" returns JSON? Do
-your controllers use a template to generate JSON? If not, what's the "view"
-then? Most MVC-style GUI web frameworks have some sort of event system
-hooked up that lets the view detect when the model changes. The web just has
-no such facility in its current form: it's effectively pull-only.
-
-So, in the interest of not mistaking desire with reality, and instead of
-trying to jam the square peg that is the web into the round hole of "MVC", we
-just punt and say there are two things: resources and views. The resource
-tree represents a site structure, the view presents a resource. The
-templates are really just an implementation detail of any given view: a view
-doesn't need a template to return a response. There's no "controller": it
-just doesn't exist. The "model" is either represented by the resource tree
-or by a "domain model" (like a SQLAlchemy model) that is separate from the
-framework entirely. This seems to us like more reasonable terminology, given
-the current constraints of the web.
+claimed in "note that the view gets its own data from the model"? Probably not.
+My "controllers" tend to do this, massaging the data for easier use by the
+"view" (template). What do you do when your "controller" returns JSON? Do your
+controllers use a template to generate JSON? If not, what's the "view" then?
+Most MVC-style GUI web frameworks have some sort of event system hooked up that
+lets the view detect when the model changes. The web just has no such facility
+in its current form; it's effectively pull-only.
+
+So, in the interest of not mistaking desire with reality, and instead of trying
+to jam the square peg that is the web into the round hole of "MVC", we just
+punt and say there are two things: resources and views. The resource tree
+represents a site structure, the view presents a resource. The templates are
+really just an implementation detail of any given view. A view doesn't need a
+template to return a response. There's no "controller"; it just doesn't exist.
+The "model" is either represented by the resource tree or by a "domain model"
+(like an SQLAlchemy model) that is separate from the framework entirely. This
+seems to us like more reasonable terminology, given the current constraints of
+the web.
+
.. _apps_are_extensible:
-Pyramid Applications are Extensible; I Don't Believe In Application Extensibility
+Pyramid Applications Are Extensible; I Don't Believe in Application Extensibility
---------------------------------------------------------------------------------
Any :app:`Pyramid` application written obeying certain constraints is
*extensible*. This feature is discussed in the :app:`Pyramid` documentation
-chapters named :ref:`extending_chapter` and :ref:`advconfig_narr`. It is
-made possible by the use of the :term:`Zope Component Architecture` and
-within :app:`Pyramid`.
+chapters named :ref:`extending_chapter` and :ref:`advconfig_narr`. It is made
+possible by the use of the :term:`Zope Component Architecture` within
+:app:`Pyramid`.
-"Extensible", in this context, means:
+"Extensible" in this context means:
-- The behavior of an application can be overridden or extended in a
- particular *deployment* of the application without requiring that
- the deployer modify the source of the original application.
+- The behavior of an application can be overridden or extended in a particular
+ *deployment* of the application without requiring that the deployer modify
+ the source of the original application.
-- The original developer is not required to anticipate any
- extensibility plugpoints at application creation time to allow
- fundamental application behavior to be overriden or extended.
+- The original developer is not required to anticipate any extensibility
+ plug points at application creation time to allow fundamental application
+ behavior to be overridden or extended.
- The original developer may optionally choose to anticipate an
- application-specific set of plugpoints, which may be hooked by
- a deployer. If he chooses to use the facilities provided by the
- ZCA, the original developer does not need to think terribly hard
- about the mechanics of introducing such a plugpoint.
+ application-specific set of plug points, which may be hooked by a deployer.
+ If they choose to use the facilities provided by the ZCA, the original
+ developer does not need to think terribly hard about the mechanics of
+ introducing such a plug point.
Many developers seem to believe that creating extensible applications is not
-worth it. They instead suggest that modifying the source of a given
-application for each deployment to override behavior is more reasonable.
-Much discussion about version control branching and merging typically ensues.
-
-It's clear that making every application extensible isn't required. The
-majority of web applications only have a single deployment, and thus needn't
-be extensible at all. However, some web applications have multiple
-deployments, and some have *many* deployments. For example, a generic
-content management system (CMS) may have basic functionality that needs to be
-extended for a particular deployment. That CMS system may be deployed for
-many organizations at many places. Some number of deployments of this CMS
-may be deployed centrally by a third party and managed as a group. It's
-easier to be able to extend such a system for each deployment via preordained
-plugpoints than it is to continually keep each software branch of the system
-in sync with some upstream source: the upstream developers may change code in
-such a way that your changes to the same codebase conflict with theirs in
-fiddly, trivial ways. Merging such changes repeatedly over the lifetime of a
-deployment can be difficult and time consuming, and it's often useful to be
-able to modify an application for a particular deployment in a less invasive
-way.
+worth it. They instead suggest that modifying the source of a given application
+for each deployment to override behavior is more reasonable. Much discussion
+about version control branching and merging typically ensues.
+
+It's clear that making every application extensible isn't required. The
+majority of web applications only have a single deployment, and thus needn't be
+extensible at all. However some web applications have multiple deployments, and
+others have *many* deployments. For example, a generic content management
+system (CMS) may have basic functionality that needs to be extended for a
+particular deployment. That CMS may be deployed for many organizations at many
+places. Some number of deployments of this CMS may be deployed centrally by a
+third party and managed as a group. It's easier to be able to extend such a
+system for each deployment via preordained plug points than it is to
+continually keep each software branch of the system in sync with some upstream
+source. The upstream developers may change code in such a way that your changes
+to the same codebase conflict with theirs in fiddly, trivial ways. Merging such
+changes repeatedly over the lifetime of a deployment can be difficult and time
+consuming, and it's often useful to be able to modify an application for a
+particular deployment in a less invasive way.
If you don't want to think about :app:`Pyramid` application extensibility at
-all, you needn't. You can ignore extensibility entirely. However, if you
-follow the set of rules defined in :ref:`extending_chapter`, you don't need
-to *make* your application extensible: any application you write in the
-framework just *is* automatically extensible at a basic level. The
-mechanisms that deployers use to extend it will be necessarily coarse:
-typically, views, routes, and resources will be capable of being
-overridden. But for most minor (and even some major) customizations, these
-are often the only override plugpoints necessary: if the application doesn't
-do exactly what the deployment requires, it's often possible for a deployer
-to override a view, route, or resource and quickly make it do what he or she
-wants it to do in ways *not necessarily anticipated by the original
-developer*. Here are some example scenarios demonstrating the benefits of
-such a feature.
-
-- If a deployment needs a different styling, the deployer may override the
- main template and the CSS in a separate Python package which defines
- overrides.
-
-- If a deployment needs an application page to do something differently, or
- to expose more or different information, the deployer may override the
- view that renders the page within a separate Python package.
+all, you needn't. You can ignore extensibility entirely. However if you follow
+the set of rules defined in :ref:`extending_chapter`, you don't need to *make*
+your application extensible. Any application you write in the framework just
+*is* automatically extensible at a basic level. The mechanisms that deployers
+use to extend it will be necessarily coarse. Typically views, routes, and
+resources will be capable of being overridden. But for most minor (and even
+some major) customizations, these are often the only override plug points
+necessary. If the application doesn't do exactly what the deployment requires,
+it's often possible for a deployer to override a view, route, or resource, and
+quickly make it do what they want it to do in ways *not necessarily anticipated
+by the original developer*. Here are some example scenarios demonstrating the
+benefits of such a feature.
+
+- If a deployment needs a different styling, the deployer may override the main
+ template and the CSS in a separate Python package which defines overrides.
+
+- If a deployment needs an application page to do something differently, or to
+ expose more or different information, the deployer may override the view that
+ renders the page within a separate Python package.
- If a deployment needs an additional feature, the deployer may add a view to
the override package.
-As long as the fundamental design of the upstream package doesn't change,
-these types of modifications often survive across many releases of the
-upstream package without needing to be revisited.
+As long as the fundamental design of the upstream package doesn't change, these
+types of modifications often survive across many releases of the upstream
+package without needing to be revisited.
Extending an application externally is not a panacea, and carries a set of
-risks similar to branching and merging: sometimes major changes upstream will
-cause you to need to revisit and update some of your modifications. But you
-won't regularly need to deal wth meaningless textual merge conflicts that
-trivial changes to upstream packages often entail when it comes time to
-update the upstream package, because if you extend an application externally,
-there just is no textual merge done. Your modifications will also, for
-whatever it's worth, be contained in one, canonical, well-defined place.
+risks similar to branching and merging. Sometimes major changes upstream will
+cause you to revisit and update some of your modifications. But you won't
+regularly need to deal with meaningless textual merge conflicts that trivial
+changes to upstream packages often entail when it comes time to update the
+upstream package, because if you extend an application externally, there just
+is no textual merge done. Your modifications will also, for whatever it's
+worth, be contained in one, canonical, well-defined place.
Branching an application and continually merging in order to get new features
-and bugfixes is clearly useful. You can do that with a :app:`Pyramid`
-application just as usefully as you can do it with any application. But
+and bug fixes is clearly useful. You can do that with a :app:`Pyramid`
+application just as usefully as you can do it with any application. But
deployment of an application written in :app:`Pyramid` makes it possible to
-avoid the need for this even if the application doesn't define any plugpoints
-ahead of time. It's possible that promoters of competing web frameworks
-dismiss this feature in favor of branching and merging because applications
-written in their framework of choice aren't extensible out of the box in a
-comparably fundamental way.
+avoid the need for this even if the application doesn't define any plug points
+ahead of time. It's possible that promoters of competing web frameworks dismiss
+this feature in favor of branching and merging because applications written in
+their framework of choice aren't extensible out of the box in a comparably
+fundamental way.
While :app:`Pyramid` applications are fundamentally extensible even if you
don't write them with specific extensibility in mind, if you're moderately
-adventurous, you can also take it a step further. If you learn more about
-the :term:`Zope Component Architecture`, you can optionally use it to expose
-other more domain-specific configuration plugpoints while developing an
-application. The plugpoints you expose needn't be as coarse as the ones
-provided automatically by :app:`Pyramid` itself. For example, you might
-compose your own directive that configures a set of views for a prebaked
-purpose (e.g. ``restview`` or somesuch) , allowing other people to refer to
-that directive when they make declarations in the ``includeme`` of their
-customization package. There is a cost for this: the developer of an
-application that defines custom plugpoints for its deployers will need to
-understand the ZCA or he will need to develop his own similar extensibility
-system.
-
-Ultimately, any argument about whether the extensibility features lent to
-applications by :app:`Pyramid` are good or bad is mostly pointless. You
-needn't take advantage of the extensibility features provided by a particular
+adventurous, you can also take it a step further. If you learn more about the
+:term:`Zope Component Architecture`, you can optionally use it to expose other
+more domain-specific configuration plug points while developing an application.
+The plug points you expose needn't be as coarse as the ones provided
+automatically by :app:`Pyramid` itself. For example, you might compose your own
+directive that configures a set of views for a pre-baked purpose (e.g.,
+``restview`` or somesuch), allowing other people to refer to that directive
+when they make declarations in the ``includeme`` of their customization
+package. There is a cost for this: the developer of an application that defines
+custom plug points for its deployers will need to understand the ZCA or they
+will need to develop their own similar extensibility system.
+
+Ultimately any argument about whether the extensibility features lent to
+applications by :app:`Pyramid` are good or bad is mostly pointless. You needn't
+take advantage of the extensibility features provided by a particular
:app:`Pyramid` application in order to affect a modification for a particular
-set of its deployments. You can ignore the application's extensibility
-plugpoints entirely, and use version control branching and merging to
-manage application deployment modifications instead, as if you were deploying
-an application written using any other web framework.
+set of its deployments. You can ignore the application's extensibility plug
+points entirely, and use version control branching and merging to manage
+application deployment modifications instead, as if you were deploying an
+application written using any other web framework.
+
-Zope 3 Enforces "TTW" Authorization Checks By Default; Pyramid Does Not
+Zope 3 Enforces "TTW" Authorization Checks by Default; Pyramid Does Not
-----------------------------------------------------------------------
Challenge
+++++++++
:app:`Pyramid` performs automatic authorization checks only at :term:`view`
-execution time. Zope 3 wraps context objects with a `security proxy
-<http://wiki.zope.org/zope3/WhatAreSecurityProxies>`_, which causes Zope 3 to
-do also security checks during attribute access. I like this, because it
-means:
+execution time. Zope 3 wraps context objects with a security proxy, which
+causes Zope 3 also to do security checks during attribute access. I like this,
+because it means:
#) When I use the security proxy machinery, I can have a view that
conditionally displays certain HTML elements (like form fields) or
@@ -892,7 +881,7 @@ web framework.
And since we tend to use the same toolkit for all web applications, it's just
never been a concern to be able to use the same set of restricted-execution
-code under two web different frameworks.
+code under two different web frameworks.
Justifications for disabling security proxies by default notwithstanding,
given that Zope 3 security proxies are viral by nature, the only requirement
@@ -905,6 +894,7 @@ Zope3-security-proxy-wrapped objects for each traversed object (including the
:term:`context` and the :term:`root`). This would have the effect of
creating a more Zope3-like environment without much effort.
+
.. _http_exception_hierarchy:
Pyramid uses its own HTTP exception class hierarchy rather than :mod:`webob.exc`
@@ -917,28 +907,29 @@ much like the ones defined in :mod:`webob.exc`, (e.g.,
:class:`~pyramid.httpexceptions.HTTPNotFound` or
:class:`~pyramid.httpexceptions.HTTPForbidden`). They have the same names and
largely the same behavior, and all have a very similar implementation, but not
-the same identity. Here's why they have a separate identity:
+the same identity. Here's why they have a separate identity.
- Making them separate allows the HTTP exception classes to subclass
:class:`pyramid.response.Response`. This speeds up response generation
- slightly due to the way the Pyramid router works. The same speedup could be
+ slightly due to the way the Pyramid router works. The same speed up could be
gained by monkeypatching :class:`webob.response.Response`, but it's usually
the case that monkeypatching turns out to be evil and wrong.
-- Making them separate allows them to provide alternate ``__call__`` logic
+- Making them separate allows them to provide alternate ``__call__`` logic,
which also speeds up response generation.
- Making them separate allows the exception classes to provide for the proper
value of ``RequestClass`` (:class:`pyramid.request.Request`).
-- Making them separate allows us freedom from having to think about backwards
- compatibility code present in :mod:`webob.exc` having to do with Python 2.4,
- which we no longer support in Pyramid 1.1+.
+- Making them separate gives us freedom from thinking about backwards
+ compatibility code present in :mod:`webob.exc` related to Python 2.4, which
+ we no longer support in Pyramid 1.1+.
- We change the behavior of two classes
(:class:`~pyramid.httpexceptions.HTTPNotFound` and
:class:`~pyramid.httpexceptions.HTTPForbidden`) in the module so that they
- can be used by Pyramid internally for notfound and forbidden exceptions.
+ can be used by Pyramid internally for ``notfound`` and ``forbidden``
+ exceptions.
- Making them separate allows us to influence the docstrings of the exception
classes to provide Pyramid-specific documentation.
@@ -947,9 +938,10 @@ the same identity. Here's why they have a separate identity:
Python 2.6 when the response objects are used as exceptions (related to
``self.message``).
+
.. _simpler_traversal_model:
-Pyramid has Simpler Traversal Machinery than Does Zope
+Pyramid has simpler traversal machinery than does Zope
------------------------------------------------------
Zope's default traverser:
@@ -959,26 +951,26 @@ Zope's default traverser:
- Attempts to use an adaptation to obtain the next element in the path from
the currently traversed object, falling back to ``__bobo_traverse__``,
- ``__getitem__`` and eventually ``__getattr__``.
+ ``__getitem__``, and eventually ``__getattr__``.
Zope's default traverser allows developers to mutate the traversal name stack
-during traversal by mutating ``REQUEST['TraversalNameStack']``. Pyramid's
-default traverser (``pyramid.traversal.ResourceTreeTraverser``) does not
-offer a way to do this; it does not maintain a stack as a request attribute
-and, even if it did, it does not pass the request to resource objects while
-it's traversing. While it was handy at times, this feature was abused in
-frameworks built atop Zope (like CMF and Plone), often making it difficult to
-tell exactly what was happening when a traversal didn't match a view. I felt
-it was better to make folks that wanted the feature replace the traverser
-rather than build that particular honey pot in to the default traverser.
+during traversal by mutating ``REQUEST['TraversalNameStack']``. Pyramid's
+default traverser (``pyramid.traversal.ResourceTreeTraverser``) does not offer
+a way to do this. It does not maintain a stack as a request attribute and, even
+if it did, it does not pass the request to resource objects while it's
+traversing. While it was handy at times, this feature was abused in frameworks
+built atop Zope (like CMF and Plone), often making it difficult to tell exactly
+what was happening when a traversal didn't match a view. I felt it was better
+for folks that wanted the feature to make them replace the traverser rather
+than build that particular honey pot in to the default traverser.
Zope uses multiple mechanisms to attempt to obtain the next element in the
resource tree based on a name. It first tries an adaptation of the current
-resource to ``ITraversable``, and if that fails, it falls back to attempting
+resource to ``ITraversable``, and if that fails, it falls back to attempting a
number of magic methods on the resource (``__bobo_traverse__``,
-``__getitem__``, and ``__getattr__``). My experience while both using Zope
-and attempting to reimplement its publisher in ``repoze.zope2`` led me to
-believe the following:
+``__getitem__``, and ``__getattr__``). My experience while both using Zope and
+attempting to reimplement its publisher in ``repoze.zope2`` led me to believe
+the following:
- The *default* traverser should be as simple as possible. Zope's publisher
is somewhat difficult to follow and replicate due to the fallbacks it tried
@@ -999,7 +991,7 @@ believe the following:
default implementation of the larger component, no one understands when (or
whether) they should ever override the larger component entrirely. This
results, over time, in a rusting together of the larger "replaceable"
- component and the framework itself, because people come to depend on the
+ component and the framework itself because people come to depend on the
availability of the default component in order just to turn its knobs. The
default component effectively becomes part of the framework, which entirely
subverts the goal of making it replaceable. In Pyramid, typically if a
@@ -1008,40 +1000,44 @@ believe the following:
you will replace the component instead of turning knobs attached to the
component.
+
.. _microframeworks_smaller_hello_world:
-Microframeworks Have Smaller Hello World Programs
+Microframeworks have smaller Hello World programs
-------------------------------------------------
-Self-described "microframeworks" exist: `Bottle <http://bottle.paws.de>`_ and
-`Flask <http://flask.pocoo.org/>`_ are two that are becoming popular. `Bobo
-<http://bobo.digicool.com/>`_ doesn't describe itself as a microframework,
-but its intended userbase is much the same. Many others exist. We've
-actually even (only as a teaching tool, not as any sort of official project)
-`created one using Pyramid <http://bfg.repoze.org/videos#groundhog1>`_ (the
-videos use BFG, a precursor to Pyramid, but the resulting code is `available
-for Pyramid too <https://github.com/Pylons/groundhog>`_). Microframeworks are
-small frameworks with one common feature: each allows its users to create a
-fully functional application that lives in a single Python file.
+Self-described "microframeworks" exist. `Bottle
+<http://bottlepy.org/docs/dev/index.html>`_ and `Flask
+<http://flask.pocoo.org/>`_ are two that are becoming popular. `Bobo
+<https://bobo.readthedocs.io/en/latest/>`_ doesn't describe itself as a
+microframework, but its intended user base is much the same. Many others exist.
+We've even (only as a teaching tool, not as any sort of official project)
+`created one using Pyramid <http://static.repoze.org/casts/videotags.html>`_.
+The videos use BFG, a precursor to Pyramid, but the resulting code is
+`available for Pyramid too <https://github.com/Pylons/groundhog>`_).
+Microframeworks are small frameworks with one common feature: each allows its
+users to create a fully functional application that lives in a single Python
+file.
Some developers and microframework authors point out that Pyramid's "hello
-world" single-file program is longer (by about five lines) than the
-equivalent program in their favorite microframework. Guilty as charged.
+world" single-file program is longer (by about five lines) than the equivalent
+program in their favorite microframework. Guilty as charged.
+
+This loss isn't for lack of trying. Pyramid is useful in the same circumstance
+in which microframeworks claim dominance: single-file applications. But Pyramid
+doesn't sacrifice its ability to credibly support larger applications in order
+to achieve "hello world" lines of code parity with the current crop of
+microframeworks. Pyramid's design instead tries to avoid some common pitfalls
+associated with naive declarative configuration schemes. The subsections which
+follow explain the rationale.
-This loss isn't for lack of trying. Pyramid is useful in the same
-circumstance in which microframeworks claim dominance: single-file
-applications. But Pyramid doesn't sacrifice its ability to credibly support
-larger applications in order to achieve hello-world LoC parity with the
-current crop of microframeworks. Pyramid's design instead tries to avoid
-some common pitfalls associated with naive declarative configuration schemes.
-The subsections which follow explain the rationale.
.. _you_dont_own_modulescope:
-Application Programmers Don't Control The Module-Scope Codepath (Import-Time Side-Effects Are Evil)
+Application programmers don't control the module-scope codepath (import-time side-effects are evil)
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-Please imagine a directory structure with a set of Python files in it:
+Imagine a directory structure with a set of Python files in it:
.. code-block:: text
@@ -1089,13 +1085,13 @@ The contents of ``config.py``:
L.append(func)
return func
-If we cd to the directory that holds these files and we run ``python app.py``
-given the directory structure and code above, what happens? Presumably, our
-``decorator`` decorator will be used twice, once by the decorated function
-``foo`` in ``app.py`` and once by the decorated function ``bar`` in
-``app2.py``. Since each time the decorator is used, the list ``L`` in
-``config.py`` is appended to, we'd expect a list with two elements to be
-printed, right? Sadly, no:
+If we ``cd`` to the directory that holds these files, and we run
+``python app.py``, given the directory structure and code above, what happens?
+Presumably, our ``decorator`` decorator will be used twice, once by the
+decorated function ``foo`` in ``app.py``, and once by the decorated function
+``bar`` in ``app2.py``. Since each time the decorator is used, the list ``L``
+in ``config.py`` is appended to, we'd expect a list with two elements to be
+printed, right? Sadly, no:
.. code-block:: text
@@ -1105,21 +1101,21 @@ printed, right? Sadly, no:
<function bar at 0x7f4ea41ab2a8>]
By visual inspection, that outcome (three different functions in the list)
-seems impossible. We only defined two functions and we decorated each of
-those functions only once, so we believe that the ``decorator`` decorator
-will only run twice. However, what we believe is wrong because the code at
-module scope in our ``app.py`` module was *executed twice*. The code is
+seems impossible. We defined only two functions, and we decorated each of those
+functions only once, so we believe that the ``decorator`` decorator will run
+only twice. However, what we believe is in fact wrong, because the code at
+module scope in our ``app.py`` module was *executed twice*. The code is
executed once when the script is run as ``__main__`` (via ``python app.py``),
and then it is executed again when ``app2.py`` imports the same file as
``app``.
-What does this have to do with our comparison to microframeworks? Many
-microframeworks in the current crop (e.g. Bottle, Flask) encourage you to
-attach configuration decorators to objects defined at module scope. These
-decorators execute arbitrarily complex registration code which populates a
-singleton registry that is a global defined in external Python module. This
-is analogous to the above example: the "global registry" in the above example
-is the list ``L``.
+What does this have to do with our comparison to microframeworks? Many
+microframeworks in the current crop (e.g., Bottle and Flask) encourage you to
+attach configuration decorators to objects defined at module scope. These
+decorators execute arbitrarily complex registration code, which populates a
+singleton registry that is a global which is in turn defined in external Python
+module. This is analogous to the above example: the "global registry" in the
+above example is the list ``L``.
Let's see what happens when we use the same pattern with the `Groundhog
<https://github.com/Pylons/groundhog>`_ microframework. Replace the contents
@@ -1172,41 +1168,39 @@ will be.
The encouragement to use decorators which perform population of an external
registry has an unintended consequence: the application developer now must
-assert ownership of every codepath that executes Python module scope
-code. Module-scope code is presumed by the current crop of decorator-based
-microframeworks to execute once and only once; if it executes more than once,
-weird things will start to happen. It is up to the application developer to
-maintain this invariant. Unfortunately, however, in reality, this is an
-impossible task, because, Python programmers *do not own the module scope
-codepath, and never will*. Anyone who tries to sell you on the idea that
-they do is simply mistaken. Test runners that you may want to use to run
-your code's tests often perform imports of arbitrary code in strange orders
-that manifest bugs like the one demonstrated above. API documentation
-generation tools do the same. Some people even think it's safe to use the
-Python ``reload`` command or delete objects from ``sys.modules``, each of
-which has hilarious effects when used against code that has import-time side
-effects.
-
-Global-registry-mutating microframework programmers therefore will at some
-point need to start reading the tea leaves about what *might* happen if
-module scope code gets executed more than once like we do in the previous
-paragraph. When Python programmers assume they can use the module-scope
-codepath to run arbitrary code (especially code which populates an external
-registry), and this assumption is challenged by reality, the application
-developer is often required to undergo a painful, meticulous debugging
-process to find the root cause of an inevitably obscure symptom. The
-solution is often to rearrange application import ordering or move an import
-statement from module-scope into a function body. The rationale for doing so
-can never be expressed adequately in the checkin message which accompanies
-the fix and can't be documented succinctly enough for the benefit of the rest
-of the development team so that the problem never happens again. It will
-happen again, especially if you are working on a project with other people
-who haven't yet internalized the lessons you learned while you stepped
-through module-scope code using ``pdb``. This is a really pretty poor
-situation to find yourself in as an application developer: you probably
-didn't even know your or your team signed up for the job, because the
-documentation offered by decorator-based microframeworks don't warn you about
-it.
+assert ownership of every code path that executes Python module scope code.
+Module-scope code is presumed by the current crop of decorator-based
+microframeworks to execute once and only once. If it executes more than once,
+weird things will start to happen. It is up to the application developer to
+maintain this invariant. Unfortunately, in reality this is an impossible task,
+because Python programmers *do not own the module scope code path, and never
+will*. Anyone who tries to sell you on the idea that they do so is simply
+mistaken. Test runners that you may want to use to run your code's tests often
+perform imports of arbitrary code in strange orders that manifest bugs like the
+one demonstrated above. API documentation generation tools do the same. Some
+people even think it's safe to use the Python ``reload`` command, or delete
+objects from ``sys.modules``, each of which has hilarious effects when used
+against code that has import-time side effects.
+
+Global registry-mutating microframework programmers therefore will at some
+point need to start reading the tea leaves about what *might* happen if module
+scope code gets executed more than once, like we do in the previous paragraph.
+When Python programmers assume they can use the module-scope code path to run
+arbitrary code (especially code which populates an external registry), and this
+assumption is challenged by reality, the application developer is often
+required to undergo a painful, meticulous debugging process to find the root
+cause of an inevitably obscure symptom. The solution is often to rearrange
+application import ordering, or move an import statement from module-scope into
+a function body. The rationale for doing so can never be expressed adequately
+in the commit message which accompanies the fix, and can't be documented
+succinctly enough for the benefit of the rest of the development team so that
+the problem never happens again. It will happen again, especially if you are
+working on a project with other people who haven't yet internalized the lessons
+you learned while you stepped through module-scope code using ``pdb``. This is
+a very poor situation in which to find yourself as an application developer:
+you probably didn't even know you or your team signed up for the job, because
+the documentation offered by decorator-based microframeworks don't warn you
+about it.
Folks who have a large investment in eager decorator-based configuration that
populates an external data structure (such as microframework authors) may
@@ -1222,7 +1216,7 @@ time, and application complexity.
If microframework authors do admit that the circumstance isn't contrived,
they might then argue that real damage will never happen as the result of the
-double-execution (or triple-execution, etc) of module scope code. You would
+double-execution (or triple-execution, etc.) of module scope code. You would
be wise to disbelieve this assertion. The potential outcomes of multiple
execution are too numerous to predict because they involve delicate
relationships between application and framework code as well as chronology of
@@ -1230,14 +1224,14 @@ code execution. It's literally impossible for a framework author to know
what will happen in all circumstances. But even if given the gift of
omniscience for some limited set of circumstances, the framework author
almost certainly does not have the double-execution anomaly in mind when
-coding new features. He's thinking of adding a feature, not protecting
+coding new features. They're thinking of adding a feature, not protecting
against problems that might be caused by the 1% multiple execution case.
However, any 1% case may cause 50% of your pain on a project, so it'd be nice
-if it never occured.
+if it never occurred.
-Responsible microframeworks actually offer a back-door way around the
-problem. They allow you to disuse decorator based configuration entirely.
-Instead of requiring you to do the following:
+Responsible microframeworks actually offer a back-door way around the problem.
+They allow you to disuse decorator-based configuration entirely. Instead of
+requiring you to do the following:
.. code-block:: python
:linenos:
@@ -1251,7 +1245,7 @@ Instead of requiring you to do the following:
if __name__ == '__main__':
gh.run()
-They allow you to disuse the decorator syntax and go almost-all-imperative:
+They allow you to disuse the decorator syntax and go almost all-imperative:
.. code-block:: python
:linenos:
@@ -1275,23 +1269,23 @@ predictability.
.. note::
- Astute readers may notice that Pyramid has configuration decorators too.
- Aha! Don't these decorators have the same problems? No. These decorators
- do not populate an external Python module when they are executed. They
- only mutate the functions (and classes and methods) they're attached to.
- These mutations must later be found during a scan process that has a
- predictable and structured import phase. Module-localized mutation is
- actually the best-case circumstance for double-imports; if a module only
- mutates itself and its contents at import time, if it is imported twice,
- that's OK, because each decorator invocation will always be mutating an
- independent copy of the object it's attached to, not a shared resource like
- a registry in another module. This has the effect that
- double-registrations will never be performed.
+ Astute readers may notice that Pyramid has configuration decorators too. Aha!
+ Don't these decorators have the same problems? No. These decorators do not
+ populate an external Python module when they are executed. They only mutate
+ the functions (and classes and methods) to which they're attached. These
+ mutations must later be found during a scan process that has a predictable
+ and structured import phase. Module-localized mutation is actually the
+ best-case circumstance for double-imports. If a module only mutates itself
+ and its contents at import time, if it is imported twice, that's OK, because
+ each decorator invocation will always be mutating an independent copy of the
+ object to which it's attached, not a shared resource like a registry in
+ another module. This has the effect that double-registrations will never be
+ performed.
.. _routes_need_ordering:
-Routes Need Relative Ordering
+Routes need relative ordering
+++++++++++++++++++++++++++++
Consider the following simple `Groundhog
@@ -1303,12 +1297,12 @@ Consider the following simple `Groundhog
from groundhog import Groundhog
app = Groundhog('myapp', 'seekrit')
- app.route('/admin')
+ @app.route('/admin')
def admin():
return '<html>admin page</html>'
- app.route('/:action')
- def action():
+ @app.route('/:action')
+ def do_action(action):
if action == 'add':
return '<html>add</html>'
if action == 'delete':
@@ -1319,8 +1313,8 @@ Consider the following simple `Groundhog
app.run()
If you run this application and visit the URL ``/admin``, you will see the
-"admin" page. This is the intended result. However, what if you rearrange
-the order of the function definitions in the file?
+"admin" page. This is the intended result. However, what if you rearrange the
+order of the function definitions in the file?
.. code-block:: python
:linenos:
@@ -1328,26 +1322,26 @@ the order of the function definitions in the file?
from groundhog import Groundhog
app = Groundhog('myapp', 'seekrit')
- app.route('/:action')
- def action():
+ @app.route('/:action')
+ def do_action(action):
if action == 'add':
return '<html>add</html>'
if action == 'delete':
return '<html>delete</html>'
return app.abort(404)
- app.route('/admin')
+ @app.route('/admin')
def admin():
return '<html>admin page</html>'
if __name__ == '__main__':
app.run()
-If you run this application and visit the URL ``/admin``, you will now be
-returned a 404 error. This is probably not what you intended. The reason
-you see a 404 error when you rearrange function definition ordering is that
-routing declarations expressed via our microframework's routing decorators
-have an *ordering*, and that ordering matters.
+If you run this application and visit the URL ``/admin``, your app will now
+return a 404 error. This is probably not what you intended. The reason you see
+a 404 error when you rearrange function definition ordering is that routing
+declarations expressed via our microframework's routing decorators have an
+*ordering*, and that ordering matters.
In the first case, where we achieved the expected result, we first added a
route with the pattern ``/admin``, then we added a route with the pattern
@@ -1355,65 +1349,67 @@ route with the pattern ``/admin``, then we added a route with the pattern
scope. When a request with a ``PATH_INFO`` of ``/admin`` enters our
application, the web framework loops over each of our application's route
patterns in the order in which they were defined in our module. As a result,
-the view associated with the ``/admin`` routing pattern will be invoked: it
-matches first. All is right with the world.
+the view associated with the ``/admin`` routing pattern will be invoked because
+it matches first. All is right with the world.
In the second case, where we did not achieve the expected result, we first
added a route with the pattern ``/:action``, then we added a route with the
pattern ``/admin``. When a request with a ``PATH_INFO`` of ``/admin`` enters
our application, the web framework loops over each of our application's route
patterns in the order in which they were defined in our module. As a result,
-the view associated with the ``/:action`` routing pattern will be invoked: it
-matches first. A 404 error is raised. This is not what we wanted; it just
-happened due to the order in which we defined our view functions.
-
-This is because Groundhog routes are added to the routing map in import
-order, and matched in the same order when a request comes in. Bottle, like
-Groundhog, as of this writing, matches routes in the order in which they're
-defined at Python execution time. Flask, on the other hand, does not order
-route matching based on import order; it reorders the routes you add to your
-application based on their "complexity". Other microframeworks have varying
+the view associated with the ``/:action`` routing pattern will be invoked
+because it matches first. A 404 error is raised. This is not what we wanted; it
+just happened due to the order in which we defined our view functions.
+
+This is because Groundhog routes are added to the routing map in import order,
+and matched in the same order when a request comes in. Bottle, like Groundhog,
+as of this writing, matches routes in the order in which they're defined at
+Python execution time. Flask, on the other hand, does not order route matching
+based on import order. Instead it reorders the routes you add to your
+application based on their "complexity". Other microframeworks have varying
strategies to do route ordering.
Your application may be small enough where route ordering will never cause an
-issue. If your application becomes large enough, however, being able to
-specify or predict that ordering as your application grows larger will be
-difficult. At some point, you will likely need to more explicitly start
-controlling route ordering, especially in applications that require
-extensibility.
+issue. If your application becomes large enough, however, being able to specify
+or predict that ordering as your application grows larger will be difficult.
+At some point, you will likely need to start controlling route ordering more
+explicitly, especially in applications that require extensibility.
If your microframework orders route matching based on complexity, you'll need
to understand what is meant by "complexity", and you'll need to attempt to
-inject a "less complex" route to have it get matched before any "more
-complex" one to ensure that it's tried first.
+inject a "less complex" route to have it get matched before any "more complex"
+one to ensure that it's tried first.
If your microframework orders its route matching based on relative
import/execution of function decorator definitions, you will need to ensure
-you execute all of these statements in the "right" order, and you'll need to
-be cognizant of this import/execution ordering as you grow your application
-or try to extend it. This is a difficult invariant to maintain for all but
-the smallest applications.
-
-In either case, your application must import the non-``__main__`` modules
-which contain configuration decorations somehow for their configuration to be
-executed. Does that make you a little uncomfortable? It should, because
+that you execute all of these statements in the "right" order, and you'll need
+to be cognizant of this import/execution ordering as you grow your application
+or try to extend it. This is a difficult invariant to maintain for all but the
+smallest applications.
+
+In either case, your application must import the non-``__main__`` modules which
+contain configuration decorations somehow for their configuration to be
+executed. Does that make you a little uncomfortable? It should, because
:ref:`you_dont_own_modulescope`.
Pyramid uses neither decorator import time ordering nor does it attempt to
-divine the relative complexity of one route to another in order to define a
-route match ordering. In Pyramid, you have to maintain relative route
-ordering imperatively via the chronology of multiple executions of the
-:meth:`pyramid.config.Configurator.add_route` method. The order in which you
+divine the relative complexity of one route to another as a means to define a
+route match ordering. In Pyramid, you have to maintain relative route ordering
+imperatively via the chronology of multiple executions of the
+:meth:`pyramid.config.Configurator.add_route` method. The order in which you
repeatedly call ``add_route`` becomes the order of route matching.
If needing to maintain this imperative ordering truly bugs you, you can use
-:term:`traversal` instead of route matching, which is a completely
-declarative (and completely predictable) mechanism to map code to URLs.
-While URL dispatch is easier to understand for small non-extensible
-applications, traversal is a great fit for very large applications and
-applications that need to be arbitrarily extensible.
+:term:`traversal` instead of route matching, which is a completely declarative
+(and completely predictable) mechanism to map code to URLs. While URL dispatch
+is easier to understand for small non-extensible applications, traversal is a
+great fit for very large applications and applications that need to be
+arbitrarily extensible.
+
-"Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance
+.. _thread_local_nuisance:
+
+"Stacked object proxies" are too clever / thread locals are a nuisance
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Some microframeworks use the ``import`` statement to get a handle to an
@@ -1436,9 +1432,10 @@ object which *is not logically global*:
# this is executed if the request method was GET or the
# credentials were invalid
-The `Pylons 1.X <http://pylonsproject.org>`_ web framework uses a similar
-strategy. It calls these things "Stacked Object Proxies", so, for purposes
-of this discussion, I'll do so as well.
+The `Pylons 1.X
+<http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/>`_
+web framework uses a similar strategy. It calls these things "Stacked Object
+Proxies", so, for purposes of this discussion, I'll do so as well.
Import statements in Python (``import foo``, ``from bar import baz``) are
most frequently performed to obtain a reference to an object defined globally
@@ -1456,32 +1453,35 @@ code below:
for i in range(10):
print(i)
-By its nature, the *request* object created as the result of a WSGI server's
-call into a long-lived web framework cannot be global, because the lifetime
-of a single request will be much shorter than the lifetime of the process
-running the framework. A request object created by a web framework actually
-has more similarity to the ``i`` loop counter in our example above than it
-has to any comparable importable object defined in the Python standard
+By its nature, the *request* object that is created as the result of a WSGI
+server's call into a long-lived web framework cannot be global, because the
+lifetime of a single request will be much shorter than the lifetime of the
+process running the framework. A request object created by a web framework
+actually has more similarity to the ``i`` loop counter in our example above
+than it has to any comparable importable object defined in the Python standard
library or in normal library code.
However, systems which use stacked object proxies promote locally scoped
-objects such as ``request`` out to module scope, for the purpose of being
+objects, such as ``request``, out to module scope, for the purpose of being
able to offer users a nice spelling involving ``import``. They, for what I
-consider dubious reasons, would rather present to their users the canonical
-way of getting at a ``request`` as ``from framework import request`` instead
-of a saner ``from myframework.threadlocals import get_request; request =
-get_request()`` even though the latter is more explicit.
+consider dubious reasons, would rather present to their users the canonical way
+of getting at a ``request`` as ``from framework import request`` instead of a
+saner ``from myframework.threadlocals import get_request; request =
+get_request()``, even though the latter is more explicit.
It would be *most* explicit if the microframeworks did not use thread local
-variables at all. Pyramid view functions are passed a request object; many
-of Pyramid's APIs require that an explicit request object be passed to them.
-It is *possible* to retrieve the current Pyramid request as a threadlocal
-variable but it is a "in case of emergency, break glass" type of activity.
-This explicitness makes Pyramid view functions more easily unit testable, as
-you don't need to rely on the framework to manufacture suitable "dummy"
-request (and other similarly-scoped) objects during test setup. It also
-makes them more likely to work on arbitrary systems, such as async servers
-that do no monkeypatching.
+variables at all. Pyramid view functions are passed a request object. Many of
+Pyramid's APIs require that an explicit request object be passed to them. It is
+*possible* to retrieve the current Pyramid request as a threadlocal variable,
+but it is an "in case of emergency, break glass" type of activity. This
+explicitness makes Pyramid view functions more easily unit testable, as you
+don't need to rely on the framework to manufacture suitable "dummy" request
+(and other similarly-scoped) objects during test setup. It also makes them
+more likely to work on arbitrary systems, such as async servers, that do no
+monkeypatching.
+
+
+.. _explicitly_wsgi:
Explicitly WSGI
+++++++++++++++
@@ -1495,35 +1495,35 @@ import a WSGI server and use it to serve up their Pyramid application as per
the documentation of that WSGI server.
The extra lines saved by abstracting away the serving step behind ``run()``
-seem to have driven dubious second-order decisions related to API in some
-microframeworks. For example, Bottle contains a ``ServerAdapter`` subclass
-for each type of WSGI server it supports via its ``app.run()`` mechanism.
-This means that there exists code in ``bottle.py`` that depends on the
-following modules: ``wsgiref``, ``flup``, ``paste``, ``cherrypy``, ``fapws``,
+seems to have driven dubious second-order decisions related to its API in some
+microframeworks. For example, Bottle contains a ``ServerAdapter`` subclass for
+each type of WSGI server it supports via its ``app.run()`` mechanism. This
+means that there exists code in ``bottle.py`` that depends on the following
+modules: ``wsgiref``, ``flup``, ``paste``, ``cherrypy``, ``fapws``,
``tornado``, ``google.appengine``, ``twisted.web``, ``diesel``, ``gevent``,
-``gunicorn``, ``eventlet``, and ``rocket``. You choose the kind of server
-you want to run by passing its name into the ``run`` method. In theory, this
-sounds great: I can try Bottle out on ``gunicorn`` just by passing in a name!
-However, to fully test Bottle, all of these third-party systems must be
-installed and functional; the Bottle developers must monitor changes to each
-of these packages and make sure their code still interfaces properly with
-them. This expands the packages required for testing greatly; this is a
-*lot* of requirements. It is likely difficult to fully automate these tests
-due to requirements conflicts and build issues.
+``gunicorn``, ``eventlet``, and ``rocket``. You choose the kind of server you
+want to run by passing its name into the ``run`` method. In theory, this sounds
+great: I can try out Bottle on ``gunicorn`` just by passing in a name! However,
+to fully test Bottle, all of these third-party systems must be installed and
+functional. The Bottle developers must monitor changes to each of these
+packages and make sure their code still interfaces properly with them. This
+increases the number of packages required for testing greatly; this is a *lot*
+of requirements. It is likely difficult to fully automate these tests due to
+requirements conflicts and build issues.
As a result, for single-file apps, we currently don't bother to offer a
-``run()`` shortcut; we tell folks to import their WSGI server of choice and
-run it by hand. For the people who want a server abstraction layer, we
-suggest that they use PasteDeploy. In PasteDeploy-based systems, the onus
-for making sure that the server can interface with a WSGI application is
-placed on the server developer, not the web framework developer, making it
-more likely to be timely and correct.
-
-Wrapping Up
+``run()`` shortcut. We tell folks to import their WSGI server of choice and run
+it by hand. For the people who want a server abstraction layer, we suggest that
+they use PasteDeploy. In PasteDeploy-based systems, the onus for making sure
+that the server can interface with a WSGI application is placed on the server
+developer, not the web framework developer, making it more likely to be timely
+and correct.
+
+Wrapping up
+++++++++++
-Here's a diagrammed version of the simplest pyramid application, where
-comments take into account what we've discussed in the
+Here's a diagrammed version of the simplest pyramid application, where the
+inlined comments take into account what we've discussed in the
:ref:`microframeworks_smaller_hello_world` section.
.. code-block:: python
@@ -1534,17 +1534,18 @@ comments take into account what we've discussed in the
def hello_world(request): # accepts a request; no request thread local reqd
# explicit response object means no response threadlocal
- return Response('Hello world!')
+ return Response('Hello world!')
if __name__ == '__main__':
from pyramid.config import Configurator
- config = Configurator() # no global application object.
+ config = Configurator() # no global application object
config.add_view(hello_world) # explicit non-decorator registration
app = config.make_wsgi_app() # explicitly WSGI
server = make_server('0.0.0.0', 8080, app)
server.serve_forever() # explicitly WSGI
-Pyramid Doesn't Offer Pluggable Apps
+
+Pyramid doesn't offer pluggable apps
------------------------------------
It is "Pyramidic" to compose multiple external sources into the same
@@ -1552,7 +1553,7 @@ configuration using :meth:`~pyramid.config.Configurator.include`. Any
number of includes can be done to compose an application; includes can even
be done from within other includes. Any directive can be used within an
include that can be used outside of one (such as
-:meth:`~pyramid.config.Configurator.add_view`, etc).
+:meth:`~pyramid.config.Configurator.add_view`).
Pyramid has a conflict detection system that will throw an error if two
included externals try to add the same configuration in a conflicting way
@@ -1652,10 +1653,11 @@ If you can understand this hello world program, you can use Pyramid:
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()
-Pyramid has ~ 700 pages of documentation (printed), covering topics from the
-very basic to the most advanced. *Nothing* is left undocumented, quite
+Pyramid has over 1200 pages of documentation (printed), covering topics from
+the very basic to the most advanced. *Nothing* is left undocumented, quite
literally. It also has an *awesome*, very helpful community. Visit the
-#pyramid IRC channel on freenode.net (irc://freenode.net#pyramid) and see.
+`#pyramid IRC channel on freenode.net
+<https://webchat.freenode.net/?channels=pyramid>`_ and see.
Hate Zope
+++++++++
@@ -1703,5 +1705,6 @@ Other Challenges
----------------
Other challenges are encouraged to be sent to the `Pylons-devel
-<http://groups.google.com/group/pylons-devel>`_ maillist. We'll try to address
-them by considering a design change, or at very least via exposition here.
+<https://groups.google.com/forum/#!forum/pylons-devel>`_ maillist. We'll try
+to address them by considering a design change, or at very least via exposition
+here.
diff --git a/docs/glossary.rst b/docs/glossary.rst
index b4bb36421..9b41b4359 100644
--- a/docs/glossary.rst
+++ b/docs/glossary.rst
@@ -36,7 +36,7 @@ Glossary
Repoze
"Repoze" is essentially a "brand" of software developed by `Agendaless
- Consulting <http://agendaless.com>`_ and a set of contributors. The
+ Consulting <https://agendaless.com>`_ and a set of contributors. The
term has no special intrinsic meaning. The project's `website
<http://repoze.org>`_ has more information. The software developed
"under the brand" is available in a `Subversion repository
@@ -51,7 +51,7 @@ Glossary
You can use :term:`distribute` under Python 3 instead.
distribute
- `Distribute <http://packages.python.org/distribute/>`_ is a fork of
+ `Distribute <https://pythonhosted.org/distribute/>`_ is a fork of
:term:`setuptools` which runs on both Python 2 and Python 3.
pkg_resources
@@ -155,9 +155,9 @@ Glossary
request before it returns a :term:`context` resource.
virtualenv
- A term referring both to an isolated Python environment,
- or `the leading tool <http://www.virtualenv.org>`_ that allows one to
- create such environments.
+ The `virtualenv tool <https://virtualenv.pypa.io/en/latest/>`_ that allows
+ one to create virtual environments. In Python 3.3 and greater,
+ :term:`venv` is the preferred tool.
Note: whenever you encounter commands prefixed with ``$VENV`` (Unix)
or ``%VENV`` (Windows), know that that is the environment variable whose
@@ -234,7 +234,7 @@ Glossary
object *location-aware*.
permission
- A string or unicode object that represents an action being taken against
+ A string or Unicode object that represents an action being taken against
a :term:`context` resource. A permission is associated with a view name
and a resource type by the developer. Resources are decorated with
security declarations (e.g. an :term:`ACL`), which reference these
@@ -273,7 +273,7 @@ Glossary
(Allow, 'bob', 'read'), (Deny, 'fred', 'write')]``. If an ACL is
attached to a resource instance, and that resource is findable via the
context resource, it will be consulted any active security policy to
- determine wither a particular request can be fulfilled given the
+ determine whether a particular request can be fulfilled given the
:term:`authentication` information in the request.
authentication
@@ -291,22 +291,22 @@ Glossary
:term:`authorization policy`.
principal
- A *principal* is a string or unicode object representing an
- entity, typically a user or group. Principals are provided by an
- :term:`authentication policy`. For example, if a user had the
- :term:`userid` `"bob"`, and was part of two groups named `"group foo"`
- and "group bar", the request might have information attached to
- it that would indicate that Bob was represented by three
- principals: `"bob"`, `"group foo"` and `"group bar"`.
+ A *principal* is a string or Unicode object representing an entity,
+ typically a user or group. Principals are provided by an
+ :term:`authentication policy`. For example, if a user has the
+ :term:`userid` `bob`, and is a member of two groups named `group foo` and
+ `group bar`, then the request might have information attached to it
+ indicating that Bob was represented by three principals: `bob`, `group
+ foo` and `group bar`.
userid
- A *userid* is a string or unicode object used to identify and
- authenticate a real-world user (or client). A userid is
- supplied to an :term:`authentication policy` in order to discover
- the user's :term:`principals <principal>`. The default behavior
- of the authentication policies :app:`Pyramid` provides is to
- return the user's userid as a principal, but this is not strictly
- necessary in custom policies that define their principals differently.
+ A *userid* is a string or Unicode object used to identify and authenticate
+ a real-world user or client. A userid is supplied to an
+ :term:`authentication policy` in order to discover the user's
+ :term:`principals <principal>`. In the authentication policies which
+ :app:`Pyramid` provides, the default behavior returns the user's userid as
+ a principal, but this is not strictly necessary in custom policies that
+ define their principals differently.
authorization policy
An authorization policy in :app:`Pyramid` terms is a bit of
@@ -321,18 +321,18 @@ Glossary
:term:`principal` (or principals) associated with a request.
WSGI
- `Web Server Gateway Interface <http://www.wsgi.org/>`_. This is a
- Python standard for connecting web applications to web servers,
- similar to the concept of Java Servlets. :app:`Pyramid` requires
- that your application be served as a WSGI application.
+ `Web Server Gateway Interface <http://wsgi.readthedocs.org/en/latest/>`_.
+ This is a Python standard for connecting web applications to web servers,
+ similar to the concept of Java Servlets. :app:`Pyramid` requires that
+ your application be served as a WSGI application.
middleware
*Middleware* is a :term:`WSGI` concept. It is a WSGI component
that acts both as a server and an application. Interesting uses
for middleware exist, such as caching, content-transport
- encoding, and other functions. See `WSGI.org <http://www.wsgi.org>`_
- or `PyPI <http://python.org/pypi>`_ to find middleware for your
- application.
+ encoding, and other functions. See `WSGI.org
+ <http://wsgi.readthedocs.org/en/latest/>`_ or `PyPI
+ <https://pypi.python.org/pypi>`_ to find middleware for your application.
pipeline
The :term:`PasteDeploy` term for a single configuration of a WSGI
@@ -346,15 +346,15 @@ Glossary
`A web framework based on Zope 3 <http://grok.zope.org>`_.
Django
- `A full-featured Python web framework <http://djangoproject.com>`_.
+ `A full-featured Python web framework <https://www.djangoproject.com/>`_.
Pylons
`A lightweight Python web framework <http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/>`_
and a predecessor of Pyramid.
ZODB
- `Zope Object Database <http://zodb.org>`_, a
- persistent Python object store.
+ `Zope Object Database <http://www.zodb.org/en/latest/>`_, a persistent
+ Python object store.
WebOb
`WebOb <http://webob.org>`_ is a WSGI request/response
@@ -367,37 +367,36 @@ Glossary
file. It was developed by Ian Bicking.
Chameleon
- `chameleon <http://chameleon.repoze.org>`_ is an attribute language
- template compiler which supports the :term:`ZPT` templating
- specification. It is written and maintained by Malthe Borch. It has
- several extensions, such as the ability to use bracketed (Mako-style)
- ``${name}`` syntax. It is also much faster than the reference
- implementation of ZPT. :app:`Pyramid` offers Chameleon templating out
- of the box in ZPT and text flavors.
+ `chameleon <https://chameleon.readthedocs.org/en/latest/>`_ is an
+ attribute language template compiler which supports the :term:`ZPT`
+ templating specification. It is written and maintained by Malthe Borch. It
+ has several extensions, such as the ability to use bracketed (Mako-style)
+ ``${name}`` syntax. It is also much faster than the reference
+ implementation of ZPT. :app:`Pyramid` offers Chameleon templating out of
+ the box in ZPT and text flavors.
ZPT
- The `Zope Page Template <http://wiki.zope.org/ZPT/FrontPage>`_
+ The `Zope Page Template <http://docs.zope.org/zope2/zope2book/ZPT.html>`_
templating language.
METAL
- `Macro Expansion for TAL <http://wiki.zope.org/ZPT/METAL>`_, a
- part of :term:`ZPT` which makes it possible to share common look
- and feel between templates.
+ `Macro Expansion for TAL
+ <http://docs.zope.org/zope2/zope2book/AppendixC.html#metal-overview>`_, a
+ part of :term:`ZPT` which makes it possible to share common look and feel
+ between templates.
Genshi
- An `XML templating language <http://pypi.python.org/pypi/Genshi/>`_
+ An `XML templating language <https://pypi.python.org/pypi/Genshi/>`_
by Christopher Lenz.
Jinja2
- A `text templating language <http://jinja.pocoo.org/2/>`_ by Armin
- Ronacher.
+ A `text templating language <http://jinja.pocoo.org/>`_ by Armin Ronacher.
Routes
- A `system by Ben Bangert <http://routes.groovie.org/>`_ which
- parses URLs and compares them against a number of user defined
- mappings. The URL pattern matching syntax in :app:`Pyramid` is
- inspired by the Routes syntax (which was inspired by Ruby On
- Rails pattern syntax).
+ A `system by Ben Bangert <http://routes.readthedocs.org/en/latest/>`_
+ which parses URLs and compares them against a number of user defined
+ mappings. The URL pattern matching syntax in :app:`Pyramid` is inspired by
+ the Routes syntax (which was inspired by Ruby On Rails pattern syntax).
route
A single pattern matched by the :term:`url dispatch` subsystem,
@@ -416,7 +415,7 @@ Glossary
Zope Component Architecture
The `Zope Component Architecture
- <http://www.muthukadan.net/docs/zca.html>`_ (aka ZCA) is a system
+ <http://muthukadan.net/docs/zca.html>`_ (aka ZCA) is a system
which allows for application pluggability and complex dispatching
based on objects which implement an :term:`interface`.
:app:`Pyramid` uses the ZCA "under the hood" to perform view
@@ -442,7 +441,7 @@ Glossary
subpath. See :ref:`star_subpath` for more information.
interface
- A `Zope interface <http://pypi.python.org/pypi/zope.interface>`_
+ A `Zope interface <https://pypi.python.org/pypi/zope.interface>`_
object. In :app:`Pyramid`, an interface may be attached to a
:term:`resource` object or a :term:`request` object in order to
identify that the object is "of a type". Interfaces are used
@@ -488,13 +487,13 @@ Glossary
repoze.catalog
An indexing and search facility (fielded and full-text) based on
- `zope.index <http://pypi.python.org/pypi/zope.index>`_. See `the
+ `zope.index <https://pypi.python.org/pypi/zope.index>`_. See `the
documentation <http://docs.repoze.org/catalog>`_ for more
information.
repoze.who
- `Authentication middleware <http://docs.repoze.org/who>`_ for
- :term:`WSGI` applications. It can be used by :app:`Pyramid` to
+ `Authentication middleware <http://repozewho.readthedocs.org/en/latest/>`_
+ for :term:`WSGI` applications. It can be used by :app:`Pyramid` to
provide authentication information.
repoze.workflow
@@ -555,25 +554,24 @@ Glossary
serialization format.
jQuery
- A popular `Javascript library <http://jquery.org>`_.
+ A popular `Javascript library <https://jquery.org>`_.
renderer
- A serializer that can be referred to via :term:`view
- configuration` which converts a non-:term:`Response` return
- values from a :term:`view` into a string (and ultimately a
- response). Using a renderer can make writing views that require
- templating or other serialization less tedious. See
- :ref:`views_which_use_a_renderer` for more information.
+ A serializer which converts non-:term:`Response` return values from a
+ :term:`view` into a string, and ultimately into a response, usually
+ through :term:`view configuration`. Using a renderer can make writing
+ views that require templating or other serialization, like JSON, less
+ tedious. See :ref:`views_which_use_a_renderer` for more information.
renderer factory
A factory which creates a :term:`renderer`. See
:ref:`adding_and_overriding_renderers` for more information.
mod_wsgi
- `mod_wsgi <http://code.google.com/p/modwsgi/>`_ is an Apache
- module developed by Graham Dumpleton. It allows :term:`WSGI`
- applications (such as applications developed using
- :app:`Pyramid`) to be served using the Apache web server.
+ `mod_wsgi <https://code.google.com/archive/p/modwsgi>`_ is an Apache
+ module developed by Graham Dumpleton. It allows :term:`WSGI` applications
+ (such as applications developed using :app:`Pyramid`) to be served using
+ the Apache web server.
view predicate
An argument to a :term:`view configuration` which evaluates to
@@ -610,7 +608,7 @@ Glossary
.. seealso::
- See also `PEP 318 <http://www.python.org/dev/peps/pep-0318/>`_.
+ See also `PEP 318 <https://www.python.org/dev/peps/pep-0318/>`_.
configuration declaration
An individual method call made to a :term:`configuration directive`,
@@ -684,7 +682,7 @@ Glossary
thread local
A thread-local variable is one which is essentially a global variable
in terms of how it is accessed and treated, however, each `thread
- <http://en.wikipedia.org/wiki/Thread_(computer_science)>`_ used by the
+ <https://en.wikipedia.org/wiki/Thread_(computer_science)>`_ used by the
application may have a different value for this same "global" variable.
:app:`Pyramid` uses a small number of thread local variables, as
described in :ref:`threadlocals_chapter`.
@@ -701,8 +699,8 @@ Glossary
:ref:`multidict_narr` and :class:`pyramid.interfaces.IMultiDict`.
PyPI
- `The Python Package Index <http://pypi.python.org/pypi>`_, a
- collection of software available for Python.
+ `The Python Package Index <https://pypi.python.org/pypi>`_, a collection
+ of software available for Python.
Agendaless Consulting
A consulting organization formed by Paul Everitt, Tres Seaver,
@@ -710,14 +708,14 @@ Glossary
.. seealso::
- See also `Agendaless Consulting <http://agendaless.com>`_.
+ See also `Agendaless Consulting <https://agendaless.com>`_.
Jython
A `Python implementation <http://www.jython.org/>`_ written for
the Java Virtual Machine.
Python
- The `programming language <http://python.org>`_ in which
+ The `programming language <https://www.python.org>`_ in which
:app:`Pyramid` is written.
CPython
@@ -737,7 +735,7 @@ Glossary
subsystems used by :app:`Pyramid`.
Google App Engine
- `Google App Engine <http://code.google.com/appengine/>`_ (aka
+ `Google App Engine <https://cloud.google.com/appengine/>`_ (aka
"GAE") is a Python application hosting service offered by Google.
:app:`Pyramid` runs on GAE.
@@ -815,11 +813,10 @@ Glossary
library, used by the :app:`Pyramid` translation machinery.
Babel
- A `collection of tools <http://babel.edgewall.org/>`_ for
- internationalizing Python applications. :app:`Pyramid` does
- not depend on Babel to operate, but if Babel is installed,
- additional locale functionality becomes available to your
- application.
+ A `collection of tools <http://babel.pocoo.org/en/latest/>`_ for
+ internationalizing Python applications. :app:`Pyramid` does not depend on
+ Babel to operate, but if Babel is installed, additional locale
+ functionality becomes available to your application.
Lingua
A package by Wichert Akkerman which provides the ``pot-create``
@@ -915,7 +912,7 @@ Glossary
can be used as global application values.
WebTest
- `WebTest <http://pythonpaste.org/webtest/>`_ is a package which can help
+ `WebTest <http://webtest.pythonpaste.org/en/latest/>`_ is a package which can help
you write functional tests for your WSGI application.
view mapper
@@ -938,20 +935,19 @@ Glossary
ZCML
`Zope Configuration Markup Language
- <http://www.muthukadan.net/docs/zca.html#zcml>`_, an XML dialect
+ <http://muthukadan.net/docs/zca.html#zcml>`_, an XML dialect
used by Zope and :term:`pyramid_zcml` for configuration tasks.
pyramid_handlers
An add-on package which allows :app:`Pyramid` users to create classes
that are analogues of Pylons 1 "controllers". See
- http://docs.pylonsproject.org/projects/pyramid_handlers/dev/ .
+ http://docs.pylonsproject.org/projects/pyramid_handlers/en/latest/.
pyramid_jinja2
:term:`Jinja2` templating system bindings for Pyramid, documented at
- http://docs.pylonsproject.org/projects/pyramid_jinja2/dev/ . This
- package also includes a scaffold named
- ``pyramid_jinja2_starter``, which creates an application package based
- on the Jinja2 templating system.
+ http://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/. This
+ package also includes a scaffold named ``pyramid_jinja2_starter``, which
+ creates an application package based on the Jinja2 templating system.
Akhet
`Akhet <http://docs.pylonsproject.org/projects/akhet/en/latest/>`_ is a
@@ -960,14 +956,14 @@ Glossary
users transition from Pylons and those preferring a more Pylons-like API.
The scaffold has been retired but the demo plays a similar role.
- Pyramid Cookbook
- Additional documentation for Pyramid which presents topical,
- practical uses of Pyramid:
- http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest.
+ Pyramid Community Cookbook
+ Additional, community-based documentation for Pyramid which presents
+ topical, practical uses of Pyramid:
+ :ref:`Pyramid Community Cookbook <cookbook:pyramid-cookbook>`
distutils
The standard system for packaging and distributing Python packages. See
- http://docs.python.org/distutils/index.html for more information.
+ https://docs.python.org/2/distutils/index.html for more information.
:term:`setuptools` is actually an *extension* of the Distutils.
exception response
@@ -1010,12 +1006,12 @@ Glossary
used in production applications, because the logger can be configured to
log to a file, to UNIX syslog, to the Windows Event Log, or even to
email. See its `documentation
- <http://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_.
+ <http://docs.pylonsproject.org/projects/pyramid_exclog/en/latest/>`_.
console script
A script written to the ``bin`` (on UNIX, or ``Scripts`` on Windows)
- directory of a Python installation or :term:`virtualenv` as the result of
- running ``setup.py install`` or ``setup.py develop``.
+ directory of a Python installation or :term:`virtual environment` as the
+ result of running ``pip install`` or ``pip install -e .``.
introspector
An object with the methods described by
@@ -1093,3 +1089,48 @@ Glossary
A technique used when serving a cacheable static asset in order to force
a client to query the new version of the asset. See :ref:`cache_busting`
for more information.
+
+ view deriver
+ A view deriver is a composable component of the view pipeline which is
+ used to create a :term:`view callable`. A view deriver is a callable
+ implementing the :class:`pyramid.interfaces.IViewDeriver` interface.
+ Examples of built-in derivers including view mapper, the permission
+ checker, and applying a renderer to a dictionary returned from the view.
+
+ truthy string
+ A string represeting a value of ``True``. Acceptable values are
+ ``t``, ``true``, ``y``, ``yes``, ``on`` and ``1``.
+
+ falsey string
+ A string represeting a value of ``False``. Acceptable values are
+ ``f``, ``false``, ``n``, ``no``, ``off`` and ``0``.
+
+ pip
+ The :term:`Python Packaging Authority`'s recommended tool for installing
+ Python packages.
+
+ pyvenv
+ The :term:`Python Packaging Authority` formerly recommended using the
+ ``pyvenv`` command for `creating virtual environments on Python 3.4 and
+ 3.5
+ <https://packaging.python.org/en/latest/installing/#creating-virtual-environments>`_,
+ but it was deprecated in 3.6 in favor of ``python3 -m venv`` on UNIX or
+ ``python -m venv`` on Windows, which is backward compatible on Python
+ 3.3 and greater.
+
+ virtual environment
+ An isolated Python environment that allows packages to be installed for
+ use by a particular application, rather than being installed system wide.
+
+ venv
+ The :term:`Python Packaging Authority`'s recommended tool for creating
+ virtual environments on Python 3.3 and greater.
+
+ Note: whenever you encounter commands prefixed with ``$VENV`` (Unix)
+ or ``%VENV`` (Windows), know that that is the environment variable whose
+ value is the root of the virtual environment in question.
+
+ Python Packaging Authority
+ The `Python Packaging Authority (PyPA) <https://www.pypa.io/en/latest/>`_
+ is a working group that maintains many of the relevant projects in Python
+ packaging. \ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index e792b9905..02c35866a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -5,7 +5,7 @@ The Pyramid Web Framework
=========================
:app:`Pyramid` is a small, fast, down-to-earth Python web framework. It is
-developed as part of the `Pylons Project <http://docs.pylonsproject.org/>`_.
+developed as part of the `Pylons Project <http://www.pylonsproject.org/>`_.
It is licensed under a `BSD-like license <http://repoze.org/license.html>`_.
Here is one of the simplest :app:`Pyramid` applications you can make:
@@ -18,7 +18,7 @@ After you install :app:`Pyramid` and run this application, when you visit
this application works.
-.. _html_getting_started:
+.. _getting_started:
Getting Started
===============
@@ -39,9 +39,9 @@ speed right away.
format, with somewhat deeper treatment of each topic and with working code.
* Like learning by example? Visit the official :ref:`html_tutorials` as well as
- the community-contributed :ref:`Pyramid tutorials
- <tutorials:pyramid-tutorials>`, which include a :ref:`Todo List Application
- in One File <tutorials:single-file-tutorial>`.
+ the community-contributed :ref:`Pyramid Tutorials
+ <tutorials:pyramid-tutorials>` and :ref:`Pyramid Community Cookbook
+ <cookbook:pyramid-cookbook>`.
* For help getting Pyramid set up, try :ref:`installing_chapter`.
@@ -60,9 +60,9 @@ platforms.
.. toctree::
:maxdepth: 1
- tutorials/wiki2/index.rst
- tutorials/wiki/index.rst
- tutorials/modwsgi/index.rst
+ tutorials/wiki2/index
+ tutorials/wiki/index
+ tutorials/modwsgi/index
.. _support-and-development:
@@ -70,15 +70,18 @@ platforms.
Support and Development
=======================
-The `Pylons Project web site <http://pylonsproject.org/>`_ is the main online
-source of :app:`Pyramid` support and development information.
+The `Pyramid website <https://trypyramid.com/resources.html>`_ is the main
+entry point to :app:`Pyramid` web framework resources for support and
+development information.
To report bugs, use the `issue tracker
<https://github.com/Pylons/pyramid/issues>`_.
If you've got questions that aren't answered by this documentation, contact the
-`Pylons-discuss maillist <http://groups.google.com/group/pylons-discuss>`_ or
-join the `#pyramid IRC channel <irc://irc.freenode.net/#pyramid>`_.
+`Pylons-discuss maillist
+<https://groups.google.com/forum/#!forum/pylons-discuss>`_ or join the
+`#pyramid IRC channel
+<https://webchat.freenode.net/?channels=pyramid>`_.
Browse and check out tagged and trunk versions of :app:`Pyramid` via the
`Pyramid GitHub repository <https://github.com/Pylons/pyramid/>`_. To check out
@@ -162,12 +165,26 @@ Comprehensive reference material for every public API exposed by
api/*
+``p*`` Scripts Documentation
+============================
+
+``p*`` scripts included with :app:`Pyramid`.
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ pscripts/index
+ pscripts/*
+
+
Change History
==============
.. toctree::
:maxdepth: 1
+ whatsnew-1.7
whatsnew-1.6
whatsnew-1.5
whatsnew-1.4
diff --git a/docs/latexindex.rst b/docs/latexindex.rst
index c4afff212..05199d313 100644
--- a/docs/latexindex.rst
+++ b/docs/latexindex.rst
@@ -14,12 +14,27 @@ Front Matter
.. toctree::
:maxdepth: 1
- copyright.rst
- conventions.rst
- authorintro.rst
+ copyright
+ conventions
+ authorintro
+ designdefense
.. mainmatter::
+.. _tutorials:
+
+Tutorials
+@@@@@@@@@
+
+.. toctree::
+ :maxdepth: 1
+
+ quick_tour
+ quick_tutorial/index
+ tutorials/wiki2/index
+ tutorials/wiki/index
+ tutorials/modwsgi/index
+
.. _narrative_documentation:
Narrative Documentation
@@ -68,31 +83,47 @@ Narrative Documentation
narr/threadlocals
narr/zca
-.. _tutorials:
-
-Tutorials
-@@@@@@@@@
+API Documentation
+@@@@@@@@@@@@@@@@@
.. toctree::
:maxdepth: 1
+ :glob:
- tutorials/wiki2/index.rst
- tutorials/wiki/index.rst
- tutorials/modwsgi/index.rst
+ api/index
+ api/*
-.. _api_documentation:
-API Documentation
-@@@@@@@@@@@@@@@@@
+``p*`` Scripts Documentation
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@
.. toctree::
:maxdepth: 1
:glob:
- api/*
+ pscripts/index
+ pscripts/*
+
.. backmatter::
+Change History
+@@@@@@@@@@@@@@
+
+.. toctree::
+ :maxdepth: 1
+
+ whatsnew-1.7
+ whatsnew-1.6
+ whatsnew-1.5
+ whatsnew-1.4
+ whatsnew-1.3
+ whatsnew-1.2
+ whatsnew-1.1
+ whatsnew-1.0
+ changes
+
+
Glossary and Index
@@@@@@@@@@@@@@@@@@
diff --git a/docs/narr/MyProject/README.txt b/docs/narr/MyProject/README.txt
index c28d0d94a..70759eba1 100644
--- a/docs/narr/MyProject/README.txt
+++ b/docs/narr/MyProject/README.txt
@@ -1 +1,12 @@
MyProject README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
+
diff --git a/docs/narr/MyProject/development.ini b/docs/narr/MyProject/development.ini
index a9a26e56b..94fece8ce 100644
--- a/docs/narr/MyProject/development.ini
+++ b/docs/narr/MyProject/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -11,7 +11,7 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
+pyramid.includes =
pyramid_debugtoolbar
# By default, the toolbar only appears for clients from IP addresses
@@ -24,12 +24,12 @@ pyramid.includes =
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/narr/MyProject/myproject/static/theme.min.css b/docs/narr/MyProject/myproject/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/narr/MyProject/myproject/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/narr/MyProject/myproject/templates/mytemplate.pt
index e6b00a145..543663fe8 100644
--- a/docs/narr/MyProject/myproject/templates/mytemplate.pt
+++ b/docs/narr/MyProject/myproject/templates/mytemplate.pt
@@ -8,12 +8,12 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('myproject:static/pyramid-16x16.png')}">
- <title>Starter Template for The Pyramid Web Framework</title>
+ <title>Starter Scaffold for The Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
- <!-- Custom styles for this template -->
+ <!-- Custom styles for this scaffold -->
<link href="${request.static_url('myproject:static/theme.css')}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
@@ -33,19 +33,20 @@
</div>
<div class="col-md-10">
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">starter template</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li class="current-version">Currently v1.5</li>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
</div>
</div>
<div class="row">
diff --git a/docs/narr/MyProject/myproject/tests.py b/docs/narr/MyProject/myproject/tests.py
index 8c60407e5..fd414cced 100644
--- a/docs/narr/MyProject/myproject/tests.py
+++ b/docs/narr/MyProject/myproject/tests.py
@@ -16,31 +16,6 @@ class ViewTests(unittest.TestCase):
info = my_view(request)
self.assertEqual(info['project'], 'MyProject')
-class ViewIntegrationTests(unittest.TestCase):
- def setUp(self):
- """ This sets up the application registry with the
- registrations your application declares in its ``includeme``
- function.
- """
- self.config = testing.setUp()
- self.config.include('myproject')
-
- def tearDown(self):
- """ Clear out the application registry """
- testing.tearDown()
-
- def test_my_view(self):
- from myproject.views import my_view
- request = testing.DummyRequest()
- result = my_view(request)
- self.assertEqual(result.status, '200 OK')
- body = result.app_iter[0]
- self.assertTrue('Welcome to' in body)
- self.assertEqual(len(result.headerlist), 2)
- self.assertEqual(result.headerlist[0],
- ('Content-Type', 'text/html; charset=UTF-8'))
- self.assertEqual(result.headerlist[1], ('Content-Length',
- str(len(body))))
class FunctionalTests(unittest.TestCase):
def setUp(self):
@@ -51,4 +26,4 @@ class FunctionalTests(unittest.TestCase):
def test_root(self):
res = self.testapp.get('/', status=200)
- self.assertTrue('Pyramid' in res.body)
+ self.assertTrue(b'Pyramid' in res.body)
diff --git a/docs/narr/MyProject/production.ini b/docs/narr/MyProject/production.ini
index 9eae9e03f..1174b1cc7 100644
--- a/docs/narr/MyProject/production.ini
+++ b/docs/narr/MyProject/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -23,7 +23,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -51,4 +51,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/narr/MyProject/setup.py b/docs/narr/MyProject/setup.py
index 9f34540a7..a911eff6d 100644
--- a/docs/narr/MyProject/setup.py
+++ b/docs/narr/MyProject/setup.py
@@ -1,30 +1,24 @@
-"""Setup for the MyProject package.
-
-"""
import os
-from setuptools import setup, find_packages
-
-
-HERE = os.path.abspath(os.path.dirname(__file__))
+from setuptools import setup, find_packages
-with open(os.path.join(HERE, 'README.txt')) as fp:
- README = fp.read()
-
-
-with open(os.path.join(HERE, 'CHANGES.txt')) as fp:
- CHANGES = fp.read()
-
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
-REQUIRES = [
+requires = [
'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
'waitress',
]
-TESTS_REQUIRE = [
- 'webtest'
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
]
setup(name='MyProject',
@@ -32,10 +26,10 @@ setup(name='MyProject',
description='MyProject',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- 'Programming Language :: Python',
- 'Framework :: Pyramid',
- 'Topic :: Internet :: WWW/HTTP',
- 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
author='',
author_email='',
@@ -44,10 +38,12 @@ setup(name='MyProject',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- install_requires=REQUIRES,
- tests_require=TESTS_REQUIRE,
- test_suite='myproject',
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
entry_points="""\
[paste.app_factory]
main = myproject:main
- """)
+ """,
+ )
diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst
index ba9bd1352..bdcdf45a4 100644
--- a/docs/narr/advconfig.rst
+++ b/docs/narr/advconfig.rst
@@ -417,7 +417,6 @@ added in configuration execution order.
More Information
----------------
-For more information, see the article `"A Whirlwind Tour of Advanced
-Configuration Tactics"
-<http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/configuration/whirlwind_tour.html>`_
-in the Pyramid Cookbook.
+For more information, see the article :ref:`A Whirlwind Tour of Advanced
+Configuration Tactics <cookbook:whirlwind-adv-conf>` in the Pyramid Community
+Cookbook.
diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst
index 0f819570c..58f547fc9 100644
--- a/docs/narr/assets.rst
+++ b/docs/narr/assets.rst
@@ -366,8 +366,7 @@ resource and requests the asset, regardless of any caching policy set for the
resource's old URL.
:app:`Pyramid` can be configured to produce cache busting URLs for static
-assets by passing the optional argument, ``cachebust`` to
-:meth:`~pyramid.config.Configurator.add_static_view`:
+assets using :meth:`~pyramid.config.Configurator.add_cache_buster`:
.. code-block:: python
:linenos:
@@ -376,14 +375,13 @@ assets by passing the optional argument, ``cachebust`` to
from pyramid.static import QueryStringConstantCacheBuster
# config is an instance of pyramid.config.Configurator
- config.add_static_view(
- name='static', path='mypackage:folder/static',
- cachebust=QueryStringConstantCacheBuster(str(int(time.time()))),
- )
+ config.add_static_view(name='static', path='mypackage:folder/static/')
+ config.add_cache_buster(
+ 'mypackage:folder/static/',
+ QueryStringConstantCacheBuster(str(int(time.time()))))
-Setting the ``cachebust`` argument instructs :app:`Pyramid` to use a cache
-busting scheme which adds the curent time for a static asset to the query
-string in the asset's URL:
+Adding the cachebuster instructs :app:`Pyramid` to add the current time for
+a static asset to the query string in the asset's URL:
.. code-block:: python
:linenos:
@@ -392,10 +390,7 @@ string in the asset's URL:
# Returns: 'http://www.example.com/static/js/myapp.js?x=1445318121'
When the web server restarts, the time constant will change and therefore so
-will its URL. Supplying the ``cachebust`` argument also causes the static
-view to set headers instructing clients to cache the asset for ten years,
-unless the ``cache_max_age`` argument is also passed, in which case that
-value is used.
+will its URL.
.. note::
@@ -410,7 +405,7 @@ Disabling the Cache Buster
It can be useful in some situations (e.g., development) to globally disable all
configured cache busters without changing calls to
-:meth:`~pyramid.config.Configurator.add_static_view`. To do this set the
+:meth:`~pyramid.config.Configurator.add_cache_buster`. To do this set the
``PYRAMID_PREVENT_CACHEBUST`` environment variable or the
``pyramid.prevent_cachebust`` configuration value to a true value.
@@ -419,9 +414,9 @@ configured cache busters without changing calls to
Customizing the Cache Buster
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The ``cachebust`` option to
-:meth:`~pyramid.config.Configurator.add_static_view` may be set to any object
-that implements the interface :class:`~pyramid.interfaces.ICacheBuster`.
+Calls to :meth:`~pyramid.config.Configurator.add_cache_buster` may use
+any object that implements the interface
+:class:`~pyramid.interfaces.ICacheBuster`.
:app:`Pyramid` ships with a very simplistic
:class:`~pyramid.static.QueryStringConstantCacheBuster`, which adds an
@@ -451,18 +446,45 @@ the hash of the current commit:
from an egg repository like PYPI, you can use this cachebuster to get
the current commit's SHA1 to use as the cache bust token.
"""
- def __init__(self, param='x'):
+ def __init__(self, param='x', repo_path=None):
super(GitCacheBuster, self).__init__(param=param)
- here = os.path.dirname(os.path.abspath(__file__))
+ if repo_path is None:
+ repo_path = os.path.dirname(os.path.abspath(__file__))
self.sha1 = subprocess.check_output(
['git', 'rev-parse', 'HEAD'],
- cwd=here).strip()
+ cwd=repo_path).strip()
def tokenize(self, pathspec):
return self.sha1
-Choosing a Cache Buster
-~~~~~~~~~~~~~~~~~~~~~~~
+A simple cache buster that modifies the path segment can be constructed as
+well:
+
+.. code-block:: python
+ :linenos:
+
+ import posixpath
+
+ class PathConstantCacheBuster(object):
+ def __init__(self, token):
+ self.token = token
+
+ def __call__(self, request, subpath, kw):
+ base_subpath, ext = posixpath.splitext(subpath)
+ new_subpath = base_subpath + self.token + ext
+ return new_subpath, kw
+
+The caveat with this approach is that modifying the path segment
+changes the file name, and thus must match what is actually on the
+filesystem in order for :meth:`~pyramid.config.Configurator.add_static_view`
+to find the file. It's better to use the
+:class:`~pyramid.static.ManifestCacheBuster` for these situations, as
+described in the next section.
+
+.. _path_segment_cache_busters:
+
+Path Segments and Choosing a Cache Buster
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Many caching HTTP proxies will fail to cache a resource if the URL contains
a query string. Therefore, in general, you should prefer a cache busting
@@ -504,28 +526,19 @@ The following code would set up a cachebuster:
config.add_static_view(
name='http://mycdn.example.com/',
- path='mypackage:static',
- cachebust=ManifestCacheBuster('myapp:static/manifest.json'))
-
-A simpler approach is to use the
-:class:`~pyramid.static.QueryStringConstantCacheBuster` to generate a global
-token that will bust all of the assets at once. The advantage of this strategy
-is that it is simple and by using the query string there doesn't need to be
-any shared information between your application and the static assets.
-
-The following code would set up a cachebuster that just uses the time at
-start up as a cachebust token:
+ path='mypackage:static')
-.. code-block:: python
- :linenos:
-
- import time
- from pyramid.static import QueryStringConstantCacheBuster
+ config.add_cache_buster(
+ 'mypackage:static/',
+ ManifestCacheBuster('myapp:static/manifest.json'))
- config.add_static_view(
- name='http://mycdn.example.com/',
- path='mypackage:static',
- cachebust=QueryStringConstantCacheBuster(str(int(time.time()))))
+It's important to note that the cache buster only handles generating
+cache-busted URLs for static assets. It does **NOT** provide any solutions for
+serving those assets. For example, if you generated a URL for
+``css/main-678b7c80.css`` then that URL needs to be valid either by
+configuring ``add_static_view`` properly to point to the location of the files
+or some other mechanism such as the files existing on your CDN or rewriting
+the incoming URL to remove the cache bust tokens.
.. index::
single: static assets view
@@ -536,25 +549,38 @@ CSS and JavaScript source and cache busting
Often one needs to refer to images and other static assets inside CSS and
JavaScript files. If cache busting is active, the final static asset URL is not
available until the static assets have been assembled. These URLs cannot be
-handwritten. Thus, when having static asset references in CSS and JavaScript,
-one needs to perform one of the following tasks:
+handwritten. Below is an example of how to integrate the cache buster into
+the entire stack. Remember, it is just an example and should be modified to
+fit your specific tools.
-* Process the files by using a precompiler which rewrites URLs to their final
- cache busted form. Then, you can use the
+* First, process the files by using a precompiler which rewrites URLs to their
+ final cache-busted form. Then, you can use the
:class:`~pyramid.static.ManifestCacheBuster` to synchronize your asset
pipeline with :app:`Pyramid`, allowing the pipeline to have full control
over the final URLs of your assets.
-* Templatize JS and CSS, and call ``request.static_url()`` inside their
- template code.
+Now that you are able to generate static URLs within :app:`Pyramid`,
+you'll need to handle URLs that are out of our control. To do this you may
+use some of the following options to get started:
-* Pass static URL references to CSS and JavaScript via other means.
+* Configure your asset pipeline to rewrite URL references inline in
+ CSS and JavaScript. This is the best approach because then the files
+ may be hosted by :app:`Pyramid` or an external CDN without having to
+ change anything. They really are static.
+
+* Templatize JS and CSS, and call ``request.static_url()`` inside their
+ template code. While this approach may work in certain scenarios, it is not
+ recommended because your static assets will not really be static and are now
+ dependent on :app:`Pyramid` to be served correctly. See
+ :ref:`advanced_static` for more information on this approach.
If your CSS and JavaScript assets use URLs to reference other assets it is
recommended that you implement an external asset pipeline that can rewrite the
generated static files with new URLs containing cache busting tokens. The
machinery inside :app:`Pyramid` will not help with this step as it has very
-little knowledge of the asset types your application may use.
+little knowledge of the asset types your application may use. The integration
+into :app:`Pyramid` is simply for linking those assets into your HTML and
+other dynamic content.
.. _advanced_static:
@@ -793,3 +819,56 @@ when an override is used.
As of Pyramid 1.6, it is also possible to override an asset by supplying an
absolute path to a file or directory. This may be useful if the assets are
not distributed as part of a Python package.
+
+Cache Busting and Asset Overrides
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Overriding static assets that are being hosted using
+:meth:`pyramid.config.Configurator.add_static_view` can affect your cache
+busting strategy when using any cache busters that are asset-aware such as
+:class:`pyramid.static.ManifestCacheBuster`. What sets asset-aware cache
+busters apart is that they have logic tied to specific assets. For example,
+a manifest is only generated for a specific set of pre-defined assets. Now,
+imagine you have overridden an asset defined in this manifest with a new,
+unknown version. By default, the cache buster will be invoked for an asset
+it has never seen before and will likely end up returning a cache busting
+token for the original asset rather than the asset that will actually end up
+being served! In order to get around this issue, it's possible to attach a
+different :class:`pyramid.interfaces.ICacheBuster` implementation to the
+new assets. This would cause the original assets to be served by their
+manifest, and the new assets served by their own cache buster. To do this,
+:meth:`pyramid.config.Configurator.add_cache_buster` supports an ``explicit``
+option. For example:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.static import ManifestCacheBuster
+
+ # define a static view for myapp:static assets
+ config.add_static_view('static', 'myapp:static')
+
+ # setup a cache buster for your app based on the myapp:static assets
+ my_cb = ManifestCacheBuster('myapp:static/manifest.json')
+ config.add_cache_buster('myapp:static', my_cb)
+
+ # override an asset
+ config.override_asset(
+ to_override='myapp:static/background.png',
+ override_with='theme:static/background.png')
+
+ # override the cache buster for theme:static assets
+ theme_cb = ManifestCacheBuster('theme:static/manifest.json')
+ config.add_cache_buster('theme:static', theme_cb, explicit=True)
+
+In the above example there is a default cache buster, ``my_cb``, for all
+assets served from the ``myapp:static`` folder. This would also affect
+``theme:static/background.png`` when generating URLs via
+``request.static_url('myapp:static/background.png')``.
+
+The ``theme_cb`` is defined explicitly for any assets loaded from the
+``theme:static`` folder. Explicit cache busters have priority and thus
+``theme_cb`` would be invoked for
+``request.static_url('myapp:static/background.png')``, but ``my_cb`` would
+be used for any other assets like
+``request.static_url('myapp:static/favicon.ico')``.
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index eb79dffb6..6cd90d42f 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -6,6 +6,7 @@ Command-Line Pyramid
Your :app:`Pyramid` application can be controlled and inspected using a variety
of command-line utilities. These utilities are documented in this chapter.
+
.. index::
pair: matching views; printing
single: pviews
@@ -15,6 +16,8 @@ of command-line utilities. These utilities are documented in this chapter.
Displaying Matching Views for a Given URL
-----------------------------------------
+.. seealso:: See also the output of :ref:`pviews --help <pviews_script>`.
+
For a big application with several views, it can be hard to keep the view
configuration details in your head, even if you defined all the views yourself.
You can use the ``pviews`` command in a terminal window to print a summary of
@@ -114,7 +117,9 @@ found* message.
The Interactive Shell
---------------------
-Once you've installed your program for development using ``setup.py develop``,
+.. seealso:: See also the output of :ref:`pshell --help <pshell_script>`.
+
+Once you've installed your program for development using ``pip install -e .``,
you can use an interactive Python shell to execute expressions in a Python
environment exactly like the one that will be used when your application runs
"for real". To do so, use the ``pshell`` command line utility.
@@ -179,6 +184,7 @@ hash after the filename:
Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows).
+
.. index::
pair: pshell; extending
@@ -261,6 +267,7 @@ request is configured to generate urls from the host
>>> request.route_url('home')
'https://www.example.com/'
+
.. _ipython_or_bpython:
Alternative Shells
@@ -287,7 +294,7 @@ You may use the ``--list-shells`` option to see the available shells.
python
If you want to use a shell that isn't supported out of the box, you can
-introduce a new shell by registering an entry point in your setup.py:
+introduce a new shell by registering an entry point in your ``setup.py``:
.. code-block:: python
@@ -317,6 +324,7 @@ arguments, ``env`` and ``help``, which would look like this:
``ipython`` and ``bpython`` have been moved into their respective
packages ``pyramid_ipython`` and ``pyramid_bpython``.
+
Setting a Default Shell
~~~~~~~~~~~~~~~~~~~~~~~
@@ -331,6 +339,7 @@ specify a list of preferred shells.
.. versionadded:: 1.6
+
.. index::
pair: routes; printing
single: proutes
@@ -340,6 +349,8 @@ specify a list of preferred shells.
Displaying All Application Routes
---------------------------------
+.. seealso:: See also the output of :ref:`proutes --help <proutes_script>`.
+
You can use the ``proutes`` command in a terminal window to print a summary of
routes related to your application. Much like the ``pshell`` command (see
:ref:`interactive_shell`), the ``proutes`` command accepts one argument with
@@ -421,6 +432,8 @@ include. The current available formats are ``name``, ``pattern``, ``view``, and
Displaying "Tweens"
-------------------
+.. seealso:: See also the output of :ref:`ptweens --help <ptweens_script>`.
+
A :term:`tween` is a bit of code that sits between the main Pyramid application
request handler and the WSGI application which calls it. A user can get a
representation of both the implicit tween ordering (the ordering specified by
@@ -497,6 +510,7 @@ used:
See :ref:`registering_tweens` for more information about tweens.
+
.. index::
single: invoking a request
single: prequest
@@ -506,6 +520,8 @@ See :ref:`registering_tweens` for more information about tweens.
Invoking a Request
------------------
+.. seealso:: See also the output of :ref:`prequest --help <prequest_script>`.
+
You can use the ``prequest`` command-line utility to send a request to your
application and see the response body without starting a server.
@@ -555,22 +571,34 @@ of the ``prequest`` process is used as the ``POST`` body::
$ $VENV/bin/prequest -mPOST development.ini / < somefile
+
Using Custom Arguments to Python when Running ``p*`` Scripts
------------------------------------------------------------
.. versionadded:: 1.5
Each of Pyramid's console scripts (``pserve``, ``pviews``, etc.) can be run
-directly using ``python -m``, allowing custom arguments to be sent to the
+directly using ``python3 -m``, allowing custom arguments to be sent to the
Python interpreter at runtime. For example::
- python -3 -m pyramid.scripts.pserve development.ini
+ python3 -m pyramid.scripts.pserve development.ini
+
+
+.. index::
+ single: pdistreport
+ single: distributions, showing installed
+ single: showing installed distributions
+
+.. _showing_distributions:
Showing All Installed Distributions and Their Versions
------------------------------------------------------
.. versionadded:: 1.5
+.. seealso:: See also the output of :ref:`pdistreport --help
+ <pdistreport_script>`.
+
You can use the ``pdistreport`` command to show the :app:`Pyramid` version in
use, the Python version in use, and all installed versions of Python
distributions in your Python environment::
@@ -590,6 +618,7 @@ pastebin when you are having problems and need someone with more familiarity
with Python packaging and distribution than you have to look at your
environment.
+
.. _writing_a_script:
Writing a Script
@@ -702,6 +731,7 @@ The above example specifies the ``another`` ``app``, ``pipeline``, or
object present in the ``env`` dictionary returned by
:func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router`.
+
Changing the Request
~~~~~~~~~~~~~~~~~~~~
@@ -742,6 +772,7 @@ Now you can readily use Pyramid's APIs for generating URLs:
env['request'].route_url('verify', code='1337')
# will return 'https://example.com/prefix/verify/1337'
+
Cleanup
~~~~~~~
@@ -757,6 +788,7 @@ callback:
env['closer']()
+
Setting Up Logging
~~~~~~~~~~~~~~~~~~
@@ -773,6 +805,7 @@ use the following command:
See :ref:`logging_chapter` for more information on logging within
:app:`Pyramid`.
+
.. index::
single: console script
@@ -782,17 +815,17 @@ Making Your Script into a Console Script
----------------------------------------
A "console script" is :term:`setuptools` terminology for a script that gets
-installed into the ``bin`` directory of a Python :term:`virtualenv` (or "base"
-Python environment) when a :term:`distribution` which houses that script is
-installed. Because it's installed into the ``bin`` directory of a virtualenv
-when the distribution is installed, it's a convenient way to package and
-distribute functionality that you can call from the command-line. It's often
-more convenient to create a console script than it is to create a ``.py``
-script and instruct people to call it with the "right" Python interpreter. A
-console script generates a file that lives in ``bin``, and when it's invoked it
-will always use the "right" Python environment, which means it will always be
-invoked in an environment where all the libraries it needs (such as Pyramid)
-are available.
+installed into the ``bin`` directory of a Python :term:`virtual environment`
+(or "base" Python environment) when a :term:`distribution` which houses that
+script is installed. Because it's installed into the ``bin`` directory of a
+virtual environment when the distribution is installed, it's a convenient way
+to package and distribute functionality that you can call from the
+command-line. It's often more convenient to create a console script than it is
+to create a ``.py`` script and instruct people to call it with the "right"
+Python interpreter. A console script generates a file that lives in ``bin``,
+and when it's invoked it will always use the "right" Python environment, which
+means it will always be invoked in an environment where all the libraries it
+needs (such as Pyramid) are available.
In general, you can make your script into a console script by doing the
following:
@@ -807,12 +840,11 @@ following:
distribution which creates a mapping between a script name and a dotted name
representing the callable you added to your distribution.
-- Run ``setup.py develop``, ``setup.py install``, or ``easy_install`` to get
- your distribution reinstalled. When you reinstall your distribution, a file
- representing the script that you named in the last step will be in the
- ``bin`` directory of the virtualenv in which you installed the distribution.
- It will be executable. Invoking it from a terminal will execute your
- callable.
+- Run ``pip install -e .`` or ``pip install .`` to get your distribution
+ reinstalled. When you reinstall your distribution, a file representing the
+ script that you named in the last step will be in the ``bin`` directory of
+ the virtual environment in which you installed the distribution. It will be
+ executable. Invoking it from a terminal will execute your callable.
As an example, let's create some code that can be invoked by a console script
that prints the deployment settings of a Pyramid application. To do so, we'll
@@ -894,16 +926,22 @@ top-level directory, your ``setup.py`` file will look something like this:
requires = ['pyramid', 'pyramid_debugtoolbar']
+ tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='MyProject',
version='0.0',
description='My project',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pylons",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -912,20 +950,23 @@ top-level directory, your ``setup.py`` file will look something like this:
include_package_data=True,
zip_safe=False,
install_requires=requires,
- tests_require=requires,
- test_suite="myproject",
+ extras_require={
+ 'testing': tests_require,
+ },
entry_points = """\
[paste.app_factory]
main = myproject:main
""",
)
-We're going to change the setup.py file to add a ``[console_scripts]`` section
-within the ``entry_points`` string. Within this section, you should specify a
-``scriptname = dotted.path.to:yourfunction`` line. For example::
+We're going to change the ``setup.py`` file to add a ``[console_scripts]``
+section within the ``entry_points`` string. Within this section, you should
+specify a ``scriptname = dotted.path.to:yourfunction`` line. For example:
- [console_scripts]
- show_settings = myproject.scripts:settings_show
+.. code-block:: ini
+
+ [console_scripts]
+ show_settings = myproject.scripts:settings_show
The ``show_settings`` name will be the name of the script that is installed
into ``bin``. The colon (``:``) between ``myproject.scripts`` and
@@ -938,6 +979,7 @@ The result will be something like:
.. code-block:: python
:linenos:
+ :emphasize-lines: 43-44
import os
@@ -951,16 +993,22 @@ The result will be something like:
requires = ['pyramid', 'pyramid_debugtoolbar']
+ tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='MyProject',
version='0.0',
description='My project',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pylons",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -969,8 +1017,9 @@ The result will be something like:
include_package_data=True,
zip_safe=False,
install_requires=requires,
- tests_require=requires,
- test_suite="myproject",
+ extras_require={
+ 'testing': tests_require,
+ },
entry_points = """\
[paste.app_factory]
main = myproject:main
@@ -979,15 +1028,17 @@ The result will be something like:
""",
)
-Once you've done this, invoking ``$$VENV/bin/python setup.py develop`` will
-install a file named ``show_settings`` into the ``$somevirtualenv/bin``
-directory with a small bit of Python code that points to your entry point. It
-will be executable. Running it without any arguments will print an error and
-exit. Running it with a single argument that is the path of a config file will
-print the settings. Running it with an ``--omit=foo`` argument will omit the
-settings that have keys that start with ``foo``. Running it with two "omit"
-options (e.g., ``--omit=foo --omit=bar``) will omit all settings that have keys
-that start with either ``foo`` or ``bar``::
+Once you've done this, invoking ``$VENV/bin/pip install -e .`` will install a
+file named ``show_settings`` into the ``$somevenv/bin`` directory with a
+small bit of Python code that points to your entry point. It will be
+executable. Running it without any arguments will print an error and exit.
+Running it with a single argument that is the path of a config file will print
+the settings. Running it with an ``--omit=foo`` argument will omit the settings
+that have keys that start with ``foo``. Running it with two "omit" options
+(e.g., ``--omit=foo --omit=bar``) will omit all settings that have keys that
+start with either ``foo`` or ``bar``:
+
+.. code-block:: bash
$ $VENV/bin/show_settings development.ini --omit=pyramid --omit=debugtoolbar
debug_routematch False
diff --git a/docs/narr/extconfig.rst b/docs/narr/extconfig.rst
index fee8d0d3a..babfa0a98 100644
--- a/docs/narr/extconfig.rst
+++ b/docs/narr/extconfig.rst
@@ -259,7 +259,9 @@ Pre-defined Phases
- :meth:`pyramid.config.Configurator.add_route_predicate`
- :meth:`pyramid.config.Configurator.add_subscriber_predicate`
- :meth:`pyramid.config.Configurator.add_view_predicate`
+- :meth:`pyramid.config.Configurator.add_view_deriver`
- :meth:`pyramid.config.Configurator.set_authorization_policy`
+- :meth:`pyramid.config.Configurator.set_default_csrf_options`
- :meth:`pyramid.config.Configurator.set_default_permission`
- :meth:`pyramid.config.Configurator.set_view_mapper`
diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst
index d28eb341d..9dc042024 100644
--- a/docs/narr/extending.rst
+++ b/docs/narr/extending.rst
@@ -197,8 +197,8 @@ this:
elements, such as templates and static assets as necessary.
- Install the new package into the same Python environment as the original
- application (e.g., ``$VENV/bin/python setup.py develop`` or
- ``$VENV/bin/python setup.py install``).
+ application (e.g., ``$VENV/bin/pip install -e .`` or ``$VENV/bin/pip install
+ .``).
- Change the ``main`` function in the new package's ``__init__.py`` to include
the original :app:`Pyramid` application's configuration functions via
diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst
index 6a952dec9..ad05976c0 100644
--- a/docs/narr/firstapp.rst
+++ b/docs/narr/firstapp.rst
@@ -27,15 +27,15 @@ installed, an HTTP server is started on TCP port 8080.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/python helloworld.py
On Windows:
-.. code-block:: text
+.. code-block:: doscon
- C:\> %VENV%\Scripts\python.exe helloworld.py
+ c:\> %VENV%\Scripts\python helloworld.py
This command will not return and nothing will be printed to the console. When
port 8080 is visited by a browser on the URL ``/hello/world``, the server will
@@ -197,7 +197,7 @@ method returns a :term:`WSGI` application object that can be used by any WSGI
server to present an application to a requestor. :term:`WSGI` is a protocol
that allows servers to talk to Python applications. We don't discuss
:term:`WSGI` in any depth within this book, but you can learn more about it by
-visiting `wsgi.org <http://wsgi.org>`_.
+reading its `documentation <http://wsgi.readthedocs.org/en/latest/>`_.
The :app:`Pyramid` application object, in particular, is an instance of a class
representing a :app:`Pyramid` :term:`router`. It has a reference to the
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 6aa1a99c2..49ef29d3f 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -262,7 +262,7 @@ already constructed a :term:`configurator`, it can also be registered via the
Adding Methods or Properties to a Request Object
------------------------------------------------
-.. versionadded:: 1.4.
+.. versionadded:: 1.4
Since each Pyramid application can only have one :term:`request` factory,
:ref:`changing the request factory <changing_the_request_factory>` is not that
@@ -300,13 +300,38 @@ added as a property and its result is cached per-request by setting
``reify=True``. This way, we eliminate the overhead of running the function
multiple times.
+.. testsetup:: group1
+
+ from pyramid.config import Configurator
+
+
+ def total(request, *args):
+ return sum(args)
+
+
+ def prop(request):
+ print("getting the property")
+ return "the property"
+
+
+
+ config = Configurator()
+ config.add_request_method(total)
+ config.add_request_method(prop, reify=True)
+ config.commit()
+
+ from pyramid.scripting import prepare
+ request = prepare(registry=config.registry)["request"]
+
+.. doctest:: group1
+
>>> request.total(1, 2, 3)
6
>>> request.prop
getting the property
- the property
+ 'the property'
>>> request.prop
- the property
+ 'the property'
To not cache the result of ``request.prop``, set ``property=True`` instead of
``reify=True``.
@@ -338,13 +363,42 @@ Here is an example of passing a class to ``Configurator.add_request_method``:
We attach and cache an object named ``extra`` to the ``request`` object.
+.. testsetup:: group2
+
+ from pyramid.config import Configurator
+ from pyramid.decorator import reify
+
+ class ExtraStuff(object):
+
+ def __init__(self, request):
+ self.request = request
+
+ def total(self, *args):
+ return sum(args)
+
+ # use @property if you don't want to cache the result
+ @reify
+ def prop(self):
+ print("getting the property")
+ return "the property"
+
+ config = Configurator()
+ config.add_request_method(ExtraStuff, 'extra', reify=True)
+ config.commit()
+
+ from pyramid.scripting import prepare
+ request = prepare(registry=config.registry)["request"]
+
+.. doctest:: group2
+
>>> request.extra.total(1, 2, 3)
6
>>> request.extra.prop
getting the property
- the property
+ 'the property'
>>> request.extra.prop
- the property
+ 'the property'
+
.. index::
single: response factory
@@ -1547,3 +1601,185 @@ in every subscriber registration. It is not the responsibility of the
predicate author to make every predicate make sense for every event type; it is
the responsibility of the predicate consumer to use predicates that make sense
for a particular event type registration.
+
+
+.. index::
+ single: view derivers
+
+.. _view_derivers:
+
+View Derivers
+-------------
+
+.. versionadded:: 1.7
+
+Every URL processed by :app:`Pyramid` is matched against a custom view
+pipeline. See :ref:`router_chapter` for how this works. The view pipeline
+itself is built from the user-supplied :term:`view callable`, which is then
+composed with :term:`view derivers <view deriver>`. A view deriver is a
+composable element of the view pipeline which is used to wrap a view with
+added functionality. View derivers are very similar to the ``decorator``
+argument to :meth:`pyramid.config.Configurator.add_view`, except that they have
+the option to execute for every view in the application.
+
+It is helpful to think of a :term:`view deriver` as middleware for views.
+Unlike tweens or WSGI middleware which are scoped to the application itself,
+a view deriver is invoked once per view in the application, and can use
+configuration options from the view to customize its behavior.
+
+Built-in View Derivers
+~~~~~~~~~~~~~~~~~~~~~~
+
+There are several built-in view derivers that :app:`Pyramid` will automatically
+apply to any view. Below they are defined in order from furthest to closest to
+the user-defined :term:`view callable`:
+
+``secured_view``
+
+ Enforce the ``permission`` defined on the view. This element is a no-op if no
+ permission is defined. Note there will always be a permission defined if a
+ default permission was assigned via
+ :meth:`pyramid.config.Configurator.set_default_permission`.
+
+ This element will also output useful debugging information when
+ ``pyramid.debug_authorization`` is enabled.
+
+``csrf_view``
+
+ Used to check the CSRF token provided in the request. This element is a
+ no-op if ``require_csrf`` view option is not ``True``. Note there will
+ always be a ``require_csrf`` option if a default value was assigned via
+ :meth:`pyramid.config.Configurator.set_default_csrf_options`.
+
+``owrapped_view``
+
+ Invokes the wrapped view defined by the ``wrapper`` option.
+
+``http_cached_view``
+
+ Applies cache control headers to the response defined by the ``http_cache``
+ option. This element is a no-op if the ``pyramid.prevent_http_cache`` setting
+ is enabled or the ``http_cache`` option is ``None``.
+
+``decorated_view``
+
+ Wraps the view with the decorators from the ``decorator`` option.
+
+``rendered_view``
+
+ Adapts the result of the :term:`view callable` into a :term:`response`
+ object. Below this point the result may be any Python object.
+
+``mapped_view``
+
+ Applies the :term:`view mapper` defined by the ``mapper`` option or the
+ application's default view mapper to the :term:`view callable`. This
+ is always the closest deriver to the user-defined view and standardizes the
+ view pipeline interface to accept ``(context, request)`` from all previous
+ view derivers.
+
+.. warning::
+
+ Any view derivers defined ``under`` the ``rendered_view`` are not
+ guaranteed to receive a valid response object. Rather they will receive the
+ result from the :term:`view mapper` which is likely the original response
+ returned from the view. This is possibly a dictionary for a renderer but it
+ may be any Python object that may be adapted into a response.
+
+Custom View Derivers
+~~~~~~~~~~~~~~~~~~~~
+
+It is possible to define custom view derivers which will affect all views in an
+application. There are many uses for this, but most will likely be centered
+around monitoring and security. In order to register a custom :term:`view
+deriver`, you should create a callable that conforms to the
+:class:`pyramid.interfaces.IViewDeriver` interface, and then register it with
+your application using :meth:`pyramid.config.Configurator.add_view_deriver`.
+For example, below is a callable that can provide timing information for the
+view pipeline:
+
+.. code-block:: python
+ :linenos:
+
+ import time
+
+ def timing_view(view, info):
+ if info.options.get('timed'):
+ def wrapper_view(context, request):
+ start = time.time()
+ response = view(context, request)
+ end = time.time()
+ response.headers['X-View-Performance'] = '%.3f' % (end - start,)
+ return response
+ return wrapper_view
+ return view
+
+ timing_view.options = ('timed',)
+
+ config.add_view_deriver(timing_view)
+
+The setting of ``timed`` on the timing_view signifies to Pyramid that ``timed``
+is a valid ``view_config`` keyword argument now. The ``timing_view`` custom
+view deriver as registered above will only be active for any view defined with
+a ``timed=True`` value passed as one of its ``view_config`` keywords.
+
+For example, this view configuration will *not* be a timed view:
+
+.. code-block:: python
+ :linenos:
+
+ @view_config(route_name='home')
+ def home(request):
+ return Response('Home')
+
+But this view *will* have timing information added to the response headers:
+
+.. code-block:: python
+ :linenos:
+
+ @view_config(route_name='home', timed=True)
+ def home(request):
+ return Response('Home')
+
+View derivers are unique in that they have access to most of the options
+passed to :meth:`pyramid.config.Configurator.add_view` in order to decide what
+to do, and they have a chance to affect every view in the application.
+
+Ordering View Derivers
+~~~~~~~~~~~~~~~~~~~~~~
+
+By default, every new view deriver is added between the ``decorated_view`` and
+``rendered_view`` built-in derivers. It is possible to customize this ordering
+using the ``over`` and ``under`` options. Each option can use the names of
+other view derivers in order to specify an ordering. There should rarely be a
+reason to worry about the ordering of the derivers except when the deriver
+depends on other operations in the view pipeline.
+
+Both ``over`` and ``under`` may also be iterables of constraints. For either
+option, if one or more constraints was defined, at least one must be satisfied,
+else a :class:`pyramid.exceptions.ConfigurationError` will be raised. This may
+be used to define fallback constraints if another deriver is missing.
+
+Two sentinel values exist, :attr:`pyramid.viewderivers.INGRESS` and
+:attr:`pyramid.viewderivers.VIEW`, which may be used when specifying
+constraints at the edges of the view pipeline. For example, to add a deriver
+at the start of the pipeline you may use ``under=INGRESS``.
+
+It is not possible to add a view deriver under the ``mapped_view`` as the
+:term:`view mapper` is intimately tied to the signature of the user-defined
+:term:`view callable`. If you simply need to know what the original view
+callable was, it can be found as ``info.original_view`` on the provided
+:class:`pyramid.interfaces.IViewDeriverInfo` object passed to every view
+deriver.
+
+.. warning::
+
+ The default constraints for any view deriver are ``over='rendered_view'``
+ and ``under='decorated_view'``. When escaping these constraints you must
+ take care to avoid cyclic dependencies between derivers. For example, if
+ you want to add a new view deriver before ``secured_view`` then
+ simply specifying ``over='secured_view'`` is not enough, because the
+ default is also under ``decorated view`` there will be an unsatisfiable
+ cycle. You must specify a valid ``under`` constraint as well, such as
+ ``under=INGRESS`` to fall between INGRESS and ``secured_view`` at the
+ beginning of the view pipeline.
diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst
index bb0bbe511..3549b53a5 100644
--- a/docs/narr/i18n.rst
+++ b/docs/narr/i18n.rst
@@ -265,18 +265,18 @@ available you can install it through the packaging system from your OS; the
package name is almost always ``gettext``. For example on a Debian or Ubuntu
system run this command:
-.. code-block:: text
+.. code-block:: bash
$ sudo apt-get install gettext
Installing Lingua is done with the Python packaging tools. If the
-:term:`virtualenv` into which you've installed your :app:`Pyramid` application
-lives in ``/my/virtualenv``, you can install Lingua like so:
+:term:`virtual environment` into which you've installed your :app:`Pyramid`
+application lives at the environment variable ``$VENV``, you can install Lingua
+like so:
-.. code-block:: text
+.. code-block:: bash
- $ cd /my/virtualenv
- $ $VENV/bin/easy_install lingua
+ $ $VENV/bin/pip install lingua
Installation on Windows
+++++++++++++++++++++++
@@ -288,12 +288,13 @@ compile it yourself. Make sure the installation path is added to your
``$PATH``.
Installing Lingua is done with the Python packaging tools. If the
-:term:`virtualenv` into which you've installed your :app:`Pyramid` application
-lives in ``C:\my\virtualenv``, you can install Lingua like so:
+:term:`virtual environment` into which you've installed your :app:`Pyramid`
+application lives at the environment variable ``%VENV%``, you can install
+Lingua like so:
-.. code-block:: text
+.. code-block:: doscon
- C> %VENV%\Scripts\easy_install lingua
+ c:\> %VENV%\Scripts\pip install lingua
.. index::
@@ -308,9 +309,9 @@ Once Lingua is installed, you may extract a message catalog template from the
code and :term:`Chameleon` templates which reside in your :app:`Pyramid`
application. You run a ``pot-create`` command to extract the messages:
-.. code-block:: text
+.. code-block:: bash
- $ cd /place/where/myapplication/setup.py/lives
+ $ cd /file/path/to/myapplication_setup.py
$ mkdir -p myapplication/locale
$ $VENV/bin/pot-create -o myapplication/locale/myapplication.pot src
@@ -331,9 +332,9 @@ represents translations of a particular set of messages to a particular locale.
Initialize a ``.po`` file for a specific locale from a pre-generated ``.pot``
template by using the ``msginit`` command from Gettext:
-.. code-block:: text
+.. code-block:: bash
- $ cd /place/where/myapplication/setup.py/lives
+ $ cd /file/path/to/myapplication_setup.py
$ cd myapplication/locale
$ mkdir -p es/LC_MESSAGES
$ msginit -l es -o es/LC_MESSAGES/myapplication.po
@@ -342,7 +343,7 @@ This will create a new message catalog ``.po`` file in
``myapplication/locale/es/LC_MESSAGES/myapplication.po``.
Once the file is there, it can be worked on by a human translator. One tool
-which may help with this is `Poedit <http://www.poedit.net/>`_.
+which may help with this is `Poedit <https://poedit.net/>`_.
Note that :app:`Pyramid` itself ignores the existence of all ``.po`` files.
For a running application to have translations available, a ``.mo`` file must
@@ -362,9 +363,9 @@ translated or re-translated.
First, regenerate the ``.pot`` file as per :ref:`extracting_messages`. Then use
the ``msgmerge`` command from Gettext.
-.. code-block:: text
+.. code-block:: bash
- $ cd /place/where/myapplication/setup.py/lives
+ $ cd /file/path/to/myapplication_setup.py
$ cd myapplication/locale
$ msgmerge --update es/LC_MESSAGES/myapplication.po myapplication.pot
@@ -380,9 +381,9 @@ Finally, to prepare an application for performing actual runtime translations,
compile ``.po`` files to ``.mo`` files using the ``msgfmt`` command from
Gettext:
-.. code-block:: text
+.. code-block:: bash
- $ cd /place/where/myapplication/setup.py/lives
+ $ cd /file/path/to/myapplication_setup.py
$ msgfmt -o myapplication/locale/es/LC_MESSAGES/myapplication.mo \
myapplication/locale/es/LC_MESSAGES/myapplication.po
@@ -585,10 +586,10 @@ Performing Date Formatting and Currency Formatting
:app:`Pyramid` does not itself perform date and currency formatting for
different locales. However, :term:`Babel` can help you do this via the
:class:`babel.core.Locale` class. The `Babel documentation for this class
-<http://babel.edgewall.org/wiki/ApiDocs/babel.core#babel.core:Locale>`_
-provides minimal information about how to perform date and currency related
-locale operations. See :ref:`installing_babel` for information about how to
-install Babel.
+<http://babel.pocoo.org/en/latest/api/core.html#basic-interface>`_ provides
+minimal information about how to perform date and currency related locale
+operations. See :ref:`installing_babel` for information about how to install
+Babel.
The :class:`babel.core.Locale` class requires a :term:`locale name` as an
argument to its constructor. You can use :app:`Pyramid` APIs to obtain the
@@ -646,7 +647,7 @@ before being rendered:
The features represented by attributes of the ``i18n`` namespace of Chameleon
will also consult the :app:`Pyramid` translations. See
-http://chameleon.readthedocs.org/en/latest/reference.html#id50.
+http://chameleon.readthedocs.org/en/latest/reference.html#translation-i18n.
.. note::
@@ -666,9 +667,24 @@ can always use the more manual translation facility described in
Mako Pyramid i18n Support
-------------------------
-There exists a recipe within the :term:`Pyramid Cookbook` named ":ref:`Mako
-Internationalization <cookbook:mako_i18n>`" which explains how to add idiomatic
-i18n support to :term:`Mako` templates.
+There exists a recipe within the :term:`Pyramid Community Cookbook` named
+:ref:`Mako Internationalization <cookbook:mako_i18n>` which explains how to add
+idiomatic i18n support to :term:`Mako` templates.
+
+
+.. index::
+ single: Jinja2 i18n
+
+Jinja2 Pyramid i18n Support
+---------------------------
+
+The add-on `pyramid_jinja2 <https://github.com/Pylons/pyramid_jinja2>`_
+provides a scaffold with an example of how to use internationalization with
+Jinja2 in Pyramid. See the documentation sections `Internalization (i18n)
+<http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#internalization-i18n>`_
+and `Paster Template I18N
+<http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/#paster-template-i18n>`_.
+
.. index::
single: localization deployment settings
diff --git a/docs/narr/install.rst b/docs/narr/install.rst
index 26d458727..c59ced2a5 100644
--- a/docs/narr/install.rst
+++ b/docs/narr/install.rst
@@ -3,38 +3,54 @@
Installing :app:`Pyramid`
=========================
+.. note::
+
+ This installation guide emphasizes the use of Python 3.4 and greater for
+ simplicity.
+
+
.. index::
single: install preparation
-Before You Install
-------------------
+Before You Install Pyramid
+--------------------------
-You will need `Python <http://python.org>`_ version 2.6 or better to run
-:app:`Pyramid`.
+Install Python version 3.4 or greater for your operating system, and satisfy
+the :ref:`requirements-for-installing-packages`, as described in
+the following sections.
.. sidebar:: Python Versions
- As of this writing, :app:`Pyramid` has been tested under Python 2.6, Python
- 2.7, Python 3.2, Python 3.3, Python 3.4, PyPy, and PyPy3. :app:`Pyramid`
- does not run under any version of Python before 2.6.
+ As of this writing, :app:`Pyramid` has been tested under Python 2.7,
+ Python 3.3, Python 3.4, Python 3.5, PyPy, and PyPy3. :app:`Pyramid` does
+ not run under any version of Python before 2.7.
:app:`Pyramid` is known to run on all popular UNIX-like systems such as Linux,
-Mac OS X, and FreeBSD as well as on Windows platforms. It is also known to run
-on :term:`PyPy` (1.9+).
+Mac OS X, and FreeBSD, as well as on Windows platforms. It is also known to
+run on :term:`PyPy` (1.9+).
-:app:`Pyramid` installation does not require the compilation of any C code, so
-you need only a Python interpreter that meets the requirements mentioned.
+:app:`Pyramid` installation does not require the compilation of any C code.
+However, some :app:`Pyramid` dependencies may attempt to build binary
+extensions from C code for performance speed ups. If a compiler or Python
+headers are unavailable, the dependency will fall back to using pure Python
+instead.
-Some :app:`Pyramid` dependencies may attempt to build C extensions for
-performance speedups. If a compiler or Python headers are unavailable the
-dependency will fall back to using pure Python instead.
+.. note::
+
+ If you see any warnings or errors related to failing to compile the binary
+ extensions, in most cases you may safely ignore those errors. If you wish to
+ use the binary extensions, please verify that you have a functioning
+ compiler and the Python header files installed for your operating system.
+
+
+.. _for-mac-os-x-users:
For Mac OS X Users
~~~~~~~~~~~~~~~~~~
Python comes pre-installed on Mac OS X, but due to Apple's release cycle, it is
often out of date. Unless you have a need for a specific earlier version, it is
-recommended to install the latest 2.x or 3.x version of Python.
+recommended to install the latest 3.x version of Python.
You can install the latest verion of Python for Mac OS X from the binaries on
`python.org <https://www.python.org/downloads/mac-osx/>`_.
@@ -43,15 +59,15 @@ Alternatively, you can use the `homebrew <http://brew.sh/>`_ package manager.
.. code-block:: text
- # for python 2.7
- $ brew install python
-
- # for python 3.4
+ # for python 3.x
$ brew install python3
If you use an installer for your Python, then you can skip to the section
:ref:`installing_unix`.
+
+.. _if-you-don-t-yet-have-a-python-interpreter-unix:
+
If You Don't Yet Have a Python Interpreter (UNIX)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -60,250 +76,122 @@ either install Python using your operating system's package manager *or* you
can install Python from source fairly easily on any UNIX system that has
development tools.
-.. index::
- pair: install; Python (from package, UNIX)
-
-Package Manager Method
-++++++++++++++++++++++
-
-You can use your system's "package manager" to install Python. Each package
-manager is slightly different, but the "flavor" of them is usually the same.
-
-For example, on a Debian or Ubuntu system, use the following command:
-
-.. code-block:: text
-
- $ sudo apt-get install python2.7-dev
-
-This command will install both the Python interpreter and its development
-header files. Note that the headers are required by some (optional) C
-extensions in software depended upon by Pyramid, not by Pyramid itself.
+.. seealso:: See the official Python documentation :ref:`Using Python on Unix
+ platforms <python:using-on-unix>` for full details.
-Once these steps are performed, the Python interpreter will usually be
-invokable via ``python2.7`` from a shell prompt.
-
-.. index::
- pair: install; Python (from source, UNIX)
-
-Source Compile Method
-+++++++++++++++++++++
-
-It's useful to use a Python interpreter that *isn't* the "system" Python
-interpreter to develop your software. The authors of :app:`Pyramid` tend not
-to use the system Python for development purposes; always a self-compiled one.
-Compiling Python is usually easy, and often the "system" Python is compiled
-with options that aren't optimal for web development. For an explanation, see
-https://github.com/Pylons/pyramid/issues/747.
-
-To compile software on your UNIX system, typically you need development tools.
-Often these can be installed via the package manager. For example, this works
-to do so on an Ubuntu Linux system:
-
-.. code-block:: text
-
- $ sudo apt-get install build-essential
-
-On Mac OS X, installing `XCode <http://developer.apple.com/tools/xcode/>`_ has
-much the same effect.
-
-Once you've got development tools installed on your system, you can install a
-Python 2.7 interpreter from *source*, on the same system, using the following
-commands:
-
-.. code-block:: text
-
- $ cd ~
- $ mkdir tmp
- $ mkdir opt
- $ cd tmp
- $ wget http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz
- $ tar xvzf Python-2.7.3.tgz
- $ cd Python-2.7.3
- $ ./configure --prefix=$HOME/opt/Python-2.7.3
- $ make && make install
-
-Once these steps are performed, the Python interpreter will be invokable via
-``$HOME/opt/Python-2.7.3/bin/python`` from a shell prompt.
.. index::
pair: install; Python (from package, Windows)
+.. _if-you-don-t-yet-have-a-python-interpreter-windows:
+
If You Don't Yet Have a Python Interpreter (Windows)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If your Windows system doesn't have a Python interpreter, you'll need to
-install it by downloading a Python 2.7-series interpreter executable from
-`python.org's download section <http://python.org/download/>`_ (the files
+install it by downloading a Python 3.x-series interpreter executable from
+`python.org's download section <https://www.python.org/downloads/>`_ (the files
labeled "Windows Installer"). Once you've downloaded it, double click on the
-executable and accept the defaults during the installation process. You may
-also need to download and install the Python for Windows extensions.
+executable, and select appropriate options during the installation process. To
+standardize this documentation, we used the GUI installer and selected the
+following options:
+
+- Screen 1: Install Python 3.x.x (32- or 64-bit)
+ - Check "Install launcher for all users (recommended)"
+ - Check "Add Python 3.x to PATH"
+ - Click "Customize installation"
+- Screen 2: Optional Features
+ - Check all options
+ - Click "Next"
+- Screen 3: Advanced Options
+ - Check all options
+ - Customize install location: "C:\\Python3x", where "x" is the minor
+ version of Python
+ - Click "Next"
+
+You might also need to download and install the Python for Windows extensions.
+
+.. seealso:: See the official Python documentation :ref:`Using Python on
+ Windows <python:using-on-windows>` for full details.
+
+.. seealso:: Download and install the `Python for Windows extensions
+ <https://sourceforge.net/projects/pywin32/files/pywin32/>`_. Carefully read
+ the README.txt file at the end of the list of builds, and follow its
+ directions. Make sure you get the proper 32- or 64-bit build and Python
+ version.
+
+.. seealso:: `Python launcher for Windows
+ <https://docs.python.org/3/using/windows.html#launcher>`_ provides a command
+ ``py`` that allows users to run any installed version of Python.
.. warning::
- After you install Python on Windows, you may need to add the ``C:\Python27``
- directory to your environment's ``Path`` in order to make it possible to
- invoke Python from a command prompt by typing ``python``. To do so, right
- click ``My Computer``, select ``Properties`` --> ``Advanced Tab`` -->
- ``Environment Variables`` and add that directory to the end of the ``Path``
+ After you install Python on Windows, you might need to add the
+ ``c:\Python3x`` directory to your environment's ``Path``, where ``x`` is the
+ minor version of installed Python, in order to make it possible to invoke
+ Python from a command prompt by typing ``python``. To do so, right click
+ ``My Computer``, select ``Properties`` --> ``Advanced Tab`` -->
+ ``Environment Variables``, and add that directory to the end of the ``Path``
environment variable.
-.. index::
- single: installing on UNIX
-
-.. _installing_unix:
-
-Installing :app:`Pyramid` on a UNIX System
-------------------------------------------
-
-It is best practice to install :app:`Pyramid` into a "virtual" Python
-environment in order to obtain isolation from any "system" packages you've got
-installed in your Python version. This can be done by using the
-:term:`virtualenv` package. Using a virtualenv will also prevent
-:app:`Pyramid` from globally installing versions of packages that are not
-compatible with your system Python.
-
-To set up a virtualenv in which to install :app:`Pyramid`, first ensure that
-:term:`setuptools` is installed. To do so, invoke ``import setuptools`` within
-the Python interpreter you'd like to run :app:`Pyramid` under.
-
-The following command will not display anything if setuptools is already
-installed:
-
-.. code-block:: text
+ .. seealso:: See `Configuring Python (on Windows)
+ <https://docs.python.org/3/using/windows.html#configuring-python>`_ for
+ full details.
- $ python2.7 -c 'import setuptools'
-
-Running the same command will yield the following output if setuptools is not
-yet installed:
-
-.. code-block:: text
-
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- ImportError: No module named setuptools
-
-If ``import setuptools`` raises an :exc:`ImportError` as it does above, you
-will need to install setuptools manually.
-
-If you are using a "system" Python (one installed by your OS distributor or a
-third-party packager such as Fink or MacPorts), you can usually install the
-setuptools package by using your system's package manager. If you cannot do
-this, or if you're using a self-installed version of Python, you will need to
-install setuptools "by hand". Installing setuptools "by hand" is always a
-reasonable thing to do, even if your package manager already has a pre-chewed
-version of setuptools for installation.
-
-Installing Setuptools
-~~~~~~~~~~~~~~~~~~~~~
-
-To install setuptools by hand under Python 2, first download `ez_setup.py
-<https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py>`_ then invoke
-it using the Python interpreter into which you want to install setuptools.
-
-.. code-block:: text
-
- $ python ez_setup.py
-
-Once this command is invoked, setuptools should be installed on your system.
-If the command fails due to permission errors, you may need to be the
-administrative user on your system to successfully invoke the script. To
-remediate this, you may need to do:
-
-.. code-block:: text
-
- $ sudo python ez_setup.py
.. index::
- pair: install; virtualenv
-
-Installing the ``virtualenv`` Package
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once you've got setuptools installed, you should install the :term:`virtualenv`
-package. To install the :term:`virtualenv` package into your
-setuptools-enabled Python interpreter, use the ``easy_install`` command.
-
-.. warning::
+ single: requirements for installing packages
- Python 3.3 includes ``pyvenv`` out of the box, which provides similar
- functionality to ``virtualenv``. We however suggest using ``virtualenv``
- instead, which works well with Python 3.3. This isn't a recommendation made
- for technical reasons; it's made because it's not feasible for the authors
- of this guide to explain setup using multiple virtual environment systems.
- We are aiming to not need to make the installation documentation
- Turing-complete.
+.. _requirements-for-installing-packages:
- If you insist on using ``pyvenv``, you'll need to understand how to install
- software such as ``setuptools`` into the virtual environment manually, which
- this guide does not cover.
+Requirements for Installing Packages
+------------------------------------
-.. code-block:: text
-
- $ easy_install virtualenv
-
-This command should succeed, and tell you that the virtualenv package is now
-installed. If it fails due to permission errors, you may need to install it as
-your system's administrative user. For example:
+Use :term:`pip` for installing packages and ``python3 -m venv env`` for
+creating a virtual environment. A virtual environment is a semi-isolated Python
+environment that allows packages to be installed for use by a particular
+application, rather than being installed system wide.
-.. code-block:: text
+.. seealso:: See the Python Packaging Authority's (PyPA) documention
+ `Requirements for Installing Packages
+ <https://packaging.python.org/en/latest/installing/#requirements-for-installing-packages>`_
+ for full details.
- $ sudo easy_install virtualenv
.. index::
- single: virtualenv
- pair: Python; virtual environment
-
-Creating the Virtual Python Environment
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once the :term:`virtualenv` package is installed in your Python environment,
-you can then create a virtual environment. To do so, invoke the following:
-
-.. code-block:: text
-
- $ export VENV=~/env
- $ virtualenv $VENV
- New python executable in /home/foo/env/bin/python
- Installing setuptools.............done.
+ single: installing on UNIX
+ single: installing on Mac OS X
-You can either follow the use of the environment variable, ``$VENV``, or
-replace it with the root directory of the :term:`virtualenv`. In that case, the
-`export` command can be skipped. If you choose the former approach, ensure that
-it's an absolute path.
+.. _installing_unix:
-.. warning::
+Installing :app:`Pyramid` on a UNIX System
+------------------------------------------
- Avoid using the ``--system-site-packages`` option when creating the
- virtualenv unless you know what you are doing. For versions of virtualenv
- prior to 1.7, make sure to use the ``--no-site-packages`` option, because
- this option was formerly not the default and may produce undesirable
- results.
+After installing Python as described previously in :ref:`for-mac-os-x-users` or
+:ref:`if-you-don-t-yet-have-a-python-interpreter-unix`, and satisfying the
+:ref:`requirements-for-installing-packages`, you can now install Pyramid.
-.. warning::
+#. Make a :term:`virtual environment` workspace:
- *do not* use ``sudo`` to run the ``virtualenv`` script. It's perfectly
- acceptable (and desirable) to create a virtualenv as a normal user.
+ .. code-block:: bash
+ $ export VENV=~/env
+ $ python3 -m venv $VENV
-Installing :app:`Pyramid` into the Virtual Python Environment
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ You can either follow the use of the environment variable ``$VENV``, or
+ replace it with the root directory of the virtual environment. If you choose
+ the former approach, ensure that ``$VENV`` is an absolute path. In the
+ latter case, the ``export`` command can be skipped.
-After you've got your virtualenv installed, you may install :app:`Pyramid`
-itself using the following commands:
+#. (Optional) Consider using ``$VENV/bin/activate`` to make your shell
+ environment wired to use the virtual environment.
-.. parsed-literal::
-
- $ $VENV/bin/easy_install "pyramid==\ |release|\ "
+#. Use ``pip`` to get :app:`Pyramid` and its direct dependencies installed:
-The ``easy_install`` command will take longer than the previous ones to
-complete, as it downloads and installs a number of dependencies.
+ .. parsed-literal::
-.. note::
+ $ $VENV/bin/pip install "pyramid==\ |release|\ "
- If you see any warnings and/or errors related to failing to compile the C
- extensions, in most cases you may safely ignore those errors. If you wish to
- use the C extensions, please verify that you have a functioning compiler and
- the Python header files installed.
.. index::
single: installing on Windows
@@ -313,72 +201,39 @@ complete, as it downloads and installs a number of dependencies.
Installing :app:`Pyramid` on a Windows System
---------------------------------------------
-You can use Pyramid on Windows under Python 2 or 3.
+After installing Python as described previously in
+:ref:`if-you-don-t-yet-have-a-python-interpreter-windows`, and satisfying the
+:ref:`requirements-for-installing-packages`, you can now install Pyramid.
-#. Download and install the most recent `Python 2.7.x or 3.3.x version
- <http://www.python.org/download/>`_ for your system.
+#. Make a :term:`virtual environment` workspace:
-#. Download and install the `Python for Windows extensions
- <http://sourceforge.net/projects/pywin32/files/pywin32/>`_. Carefully read
- the README.txt file at the end of the list of builds, and follow its
- directions. Make sure you get the proper 32- or 64-bit build and Python
- version.
-
-#. Install latest :term:`setuptools` distribution into the Python from step 1
- above: download `ez_setup.py
- <https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py>`_ and run
- it using the ``python`` interpreter of your Python 2.7 or 3.3 installation
- using a command prompt:
-
- .. code-block:: text
-
- # modify the command according to the python version, e.g.:
- # for Python 2.7:
- c:\> c:\Python27\python ez_setup.py
- # for Python 3.3:
- c:\> c:\Python33\python ez_setup.py
-
-#. Install `virtualenv`:
-
- .. code-block:: text
-
- # modify the command according to the python version, e.g.:
- # for Python 2.7:
- c:\> c:\Python27\Scripts\easy_install virtualenv
- # for Python 3.3:
- c:\> c:\Python33\Scripts\easy_install virtualenv
-
-#. Make a :term:`virtualenv` workspace:
-
- .. code-block:: text
+ .. code-block:: doscon
c:\> set VENV=c:\env
- # modify the command according to the python version, e.g.:
- # for Python 2.7:
- c:\> c:\Python27\Scripts\virtualenv %VENV%
- # for Python 3.3:
- c:\> c:\Python33\Scripts\virtualenv %VENV%
+ # replace "x" with your minor version of Python 3
+ c:\> c:\Python3x\python -m venv %VENV%
+ c:\> cd %VENV%
- You can either follow the use of the environment variable, ``%VENV%``, or
- replace it with the root directory of the :term:`virtualenv`. In that case,
- the `set` command can be skipped. If you choose the former approach, ensure
- that it's an absolute path.
+ You can either follow the use of the environment variable ``%VENV%``, or
+ replace it with the root directory of the virtual environment. If you choose
+ the former approach, ensure that ``%VENV%`` is an absolute path. In the
+ latter case, the ``set`` command can be skipped.
#. (Optional) Consider using ``%VENV%\Scripts\activate.bat`` to make your shell
- environment wired to use the virtualenv.
+ environment wired to use the virtual environment.
-#. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies
- installed:
+#. Use ``pip`` to get :app:`Pyramid` and its direct dependencies installed:
.. parsed-literal::
-
- c:\\env> %VENV%\\Scripts\\easy_install "pyramid==\ |release|\ "
+
+ c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
+
What Gets Installed
-------------------
-When you ``easy_install`` :app:`Pyramid`, various other libraries such as
-WebOb, PasteDeploy, and others are installed.
+When you install :app:`Pyramid`, various libraries such as WebOb, PasteDeploy,
+and others are installed.
Additionally, as chronicled in :ref:`project_narr`, scaffolds will be
registered, which make it easy to start a new :app:`Pyramid` project.
diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst
index 7906dd85d..de6ac408b 100644
--- a/docs/narr/introduction.rst
+++ b/docs/narr/introduction.rst
@@ -221,7 +221,7 @@ send email, let you use the Jinja2 templating system, let you use XML-RPC or
JSON-RPC, let you integrate with jQuery Mobile, etc.
Examples:
-http://docs.pylonsproject.org/en/latest/docs/pyramid.html#pyramid-add-on-documentation
+https://trypyramid.com/resources-extending-pyramid.html
Class-based and function-based views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -859,14 +859,15 @@ Testing
Every release of Pyramid has 100% statement coverage via unit and integration
tests, as measured by the ``coverage`` tool available on PyPI. It also has
greater than 95% decision/condition coverage as measured by the
-``instrumental`` tool available on PyPI. It is automatically tested by the
-Jenkins tool on Python 2.6, Python 2.7, Python 3.2, Python 3.3, Python 3.4,
-PyPy, and PyPy3 after each commit to its GitHub repository. Official Pyramid
-add-ons are held to a similar testing standard. We still find bugs in Pyramid
-and its official add-ons, but we've noticed we find a lot more of them while
-working on other projects that don't have a good testing regime.
+``instrumental`` tool available on PyPI. It is automatically tested by Travis,
+and Jenkins on Python 2.7, Python 3.3, Python 3.4, Python 3.5, PyPy, and PyPy3
+after each commit to its GitHub repository. Official Pyramid add-ons are held
+to a similar testing standard. We still find bugs in Pyramid and its official
+add-ons, but we've noticed we find a lot more of them while working on other
+projects that don't have a good testing regime.
-Example: http://jenkins.pylonsproject.org/
+Travis: https://travis-ci.org/Pylons/pyramid
+Jenkins: http://jenkins.pylonsproject.org/job/pyramid/
Support
~~~~~~~
@@ -880,7 +881,7 @@ new-user-friendly.
Example: Visit irc\://freenode.net#pyramid (the ``#pyramid`` channel on
irc.freenode.net in an IRC client) or the pylons-discuss maillist at
-http://groups.google.com/group/pylons-discuss/.
+https://groups.google.com/forum/#!forum/pylons-discuss.
Documentation
~~~~~~~~~~~~~
@@ -892,8 +893,7 @@ also maintain a "cookbook" of recipes, which are usually demonstrations of
common integration scenarios too specific to add to the official narrative
docs. In any case, the Pyramid documentation is comprehensive.
-Example: The Pyramid Cookbook at
-http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/.
+Example: The :ref:`Pyramid Community Cookbook <cookbook:pyramid-cookbook>`.
.. index::
single: Pylons Project
@@ -903,7 +903,7 @@ What Is The Pylons Project?
:app:`Pyramid` is a member of the collection of software published under the
Pylons Project. Pylons software is written by a loose-knit community of
-contributors. The `Pylons Project website <http://pylonsproject.org>`_
+contributors. The `Pylons Project website <http://www.pylonsproject.org>`_
includes details about how :app:`Pyramid` relates to the Pylons Project.
.. index::
@@ -967,9 +967,9 @@ nor discouraging the decision.
Other Python web frameworks advertise themselves as members of a class of web
frameworks named `model-view-controller
-<http://en.wikipedia.org/wiki/Model–view–controller>`_ frameworks. Insofar as
-this term has been claimed to represent a class of web frameworks,
-:app:`Pyramid` also generally fits into this class.
+<https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>`_
+frameworks. Insofar as this term has been claimed to represent a class of web
+frameworks, :app:`Pyramid` also generally fits into this class.
.. sidebar:: You Say :app:`Pyramid` is MVC, but Where's the Controller?
diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst
index 98315ac9f..c9fecd4f4 100644
--- a/docs/narr/introspector.rst
+++ b/docs/narr/introspector.rst
@@ -337,6 +337,31 @@ introspectables in categories not described here.
The permission name passed to ``set_default_permission``.
+``default csrf options``
+
+ There will be one and only one introspectable in the ``default csrf options``
+ category. It represents a call to the
+ :meth:`pyramid.config.Configurator.set_default_csrf_options` method. It
+ will have the following data.
+
+ ``require_csrf``
+
+ The default value for ``require_csrf`` if left unspecified on calls to
+ :meth:`pyramid.config.Configurator.add_view`.
+
+ ``token``
+
+ The name of the token searched in ``request.POST`` to find a valid CSRF
+ token.
+
+ ``header``
+
+ The name of the request header searched to find a valid CSRF token.
+
+ ``safe_methods``
+
+ The list of HTTP methods considered safe and exempt from CSRF checks.
+
``views``
Each introspectable in the ``views`` category represents a call to
diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst
index 9c6e8a319..c7b4b9d6f 100644
--- a/docs/narr/logging.rst
+++ b/docs/narr/logging.rst
@@ -292,7 +292,8 @@ Logging Exceptions
To log or email exceptions generated by your :app:`Pyramid` application, use
the :term:`pyramid_exclog` package. Details about its configuration are in its
-`documentation <http://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_.
+`documentation
+<http://docs.pylonsproject.org/projects/pyramid_exclog/en/latest/>`_.
.. index::
single: TransLogger
diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst
index 3e00a295a..02cd8ee3a 100644
--- a/docs/narr/muchadoabouttraversal.rst
+++ b/docs/narr/muchadoabouttraversal.rst
@@ -8,9 +8,7 @@ Much Ado About Traversal
.. note::
- This chapter was adapted, with permission, from a blog post by `Rob Miller
- <http://blog.nonsequitarian.org/>`_, originally published at
- http://blog.nonsequitarian.org/2010/much-ado-about-traversal/.
+ This chapter was adapted, with permission, from a blog post by Rob Miller.
Traversal is an alternative to :term:`URL dispatch` which allows :app:`Pyramid`
applications to map URLs to code.
diff --git a/docs/narr/project.rst b/docs/narr/project.rst
index 25f3931e9..71bd176f6 100644
--- a/docs/narr/project.rst
+++ b/docs/narr/project.rst
@@ -53,57 +53,50 @@ The included scaffolds are these:
``alchemy``
URL mapping via :term:`URL dispatch` and persistence via :term:`SQLAlchemy`
+
.. index::
single: creating a project
single: project
+ single: pcreate
.. _creating_a_project:
Creating the Project
--------------------
+.. seealso:: See also the output of :ref:`pcreate --help <pcreate_script>`.
+
In :ref:`installing_chapter`, you created a virtual Python environment via the
-``virtualenv`` command. To start a :app:`Pyramid` :term:`project`, use the
-``pcreate`` command installed within the virtualenv. We'll choose the
+``venv`` command. To start a :app:`Pyramid` :term:`project`, use the
+``pcreate`` command installed within the virtual environment. We'll choose the
``starter`` scaffold for this purpose. When we invoke ``pcreate``, it will
create a directory that represents our project.
-In :ref:`installing_chapter` we called the virtualenv directory ``env``. The
-following commands assume that our current working directory is the ``env``
-directory.
+In :ref:`installing_chapter` we called the virtual environment directory
+``env``. The following commands assume that our current working directory is
+the ``env`` directory.
The below example uses the ``pcreate`` command to create a project with the
``starter`` scaffold.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/pcreate -s starter MyProject
Or on Windows:
-.. code-block:: text
-
- > %VENV%\Scripts\pcreate -s starter MyProject
-
-Here's sample output from a run of ``pcreate`` on UNIX for a project we name
-``MyProject``:
+.. code-block:: doscon
-.. code-block:: text
-
- $ $VENV/bin/pcreate -s starter MyProject
- Creating template pyramid
- Creating directory ./MyProject
- # ... more output ...
- Running /Users/chrism/projects/pyramid/bin/python setup.py egg_info
+ c:\> %VENV%\Scripts\pcreate -s starter MyProject
As a result of invoking the ``pcreate`` command, a directory named
``MyProject`` is created. That directory is a :term:`project` directory. The
``setup.py`` file in that directory can be used to distribute your application,
or install your application for deployment or development.
-A ``.ini`` file named ``development.ini`` will be created in the project
+An ``.ini`` file named ``development.ini`` will be created in the project
directory. You will use this ``.ini`` file to configure a server, to run your
application, and to debug your application. It contains configuration that
enables an interactive debugger and settings optimized for development.
@@ -119,16 +112,16 @@ The ``MyProject`` project directory contains an additional subdirectory named
which holds very simple :app:`Pyramid` sample code. This is where you'll edit
your application's Python code and templates.
-We created this project within an ``env`` virtualenv directory. However, note
-that this is not mandatory. The project directory can go more or less anywhere
-on your filesystem. You don't need to put it in a special "web server"
-directory, and you don't need to put it within a virtualenv directory. The
-author uses Linux mainly, and tends to put project directories which he creates
-within his ``~/projects`` directory. On Windows, it's a good idea to put
-project directories within a directory that contains no space characters, so
-it's wise to *avoid* a path that contains, i.e., ``My Documents``. As a
-result, the author, when he uses Windows, just puts his projects in
-``C:\projects``.
+We created this project within an ``env`` virtual environment directory.
+However, note that this is not mandatory. The project directory can go more or
+less anywhere on your filesystem. You don't need to put it in a special "web
+server" directory, and you don't need to put it within a virtual environment
+directory. The author uses Linux mainly, and tends to put project directories
+which he creates within his ``~/projects`` directory. On Windows, it's a good
+idea to put project directories within a directory that contains no space
+characters, so it's wise to *avoid* a path that contains, i.e., ``My
+Documents``. As a result, the author, when he uses Windows, just puts his
+projects in ``C:\projects``.
.. warning::
@@ -147,8 +140,9 @@ Installing your Newly Created Project for Development
To install a newly created project for development, you should ``cd`` to the
newly created project directory and use the Python interpreter from the
-:term:`virtualenv` you created during :ref:`installing_chapter` to invoke the
-command ``python setup.py develop``
+:term:`virtual environment` you created during :ref:`installing_chapter` to
+invoke the command ``pip install -e .``, which installs the project in
+development mode (``-e`` is for "editable") into the current directory (``.``).
The file named ``setup.py`` will be in the root of the pcreate-generated
project directory. The ``python`` you're invoking should be the one that lives
@@ -158,26 +152,27 @@ created project directory.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ cd MyProject
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
Or on Windows:
-.. code-block:: text
+.. code-block:: doscon
- > cd MyProject
- > %VENV%\Scripts\python.exe setup.py develop
+ c:\> cd MyProject
+ c:\> %VENV%\Scripts\pip install -e .
Elided output from a run of this command on UNIX is shown below:
-.. code-block:: text
+.. code-block:: bash
$ cd MyProject
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
...
- Finished processing dependencies for MyProject==0.0
+ Successfully installed Chameleon-2.24 Mako-1.0.4 MyProject \
+ pyramid-chameleon-0.3 pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2
This will install a :term:`distribution` representing your project into the
virtual environment interpreter's library set so it can be found by ``import``
@@ -191,53 +186,74 @@ statements and by other console scripts such as ``pserve``, ``pshell``,
Running the Tests for Your Application
--------------------------------------
-To run unit tests for your application, you should invoke them using the Python
-interpreter from the :term:`virtualenv` you created during
-:ref:`installing_chapter` (the ``python`` command that lives in the ``bin``
-directory of your virtualenv).
+To run unit tests for your application, you must first install the testing
+dependencies.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/python setup.py test -q
+ $ $VENV/bin/pip install -e ".[testing]"
-Or on Windows:
+On Windows:
-.. code-block:: text
+.. code-block:: doscon
+
+ c:\> %VENV%\Scripts\pip install -e ".[testing]"
+
+Once the testing requirements are installed, then you can run the tests using
+the ``py.test`` command that was just installed in the ``bin`` directory of
+your virtual environment.
+
+On UNIX:
+
+.. code-block:: bash
+
+ $ $VENV/bin/py.test -q
- > %VENV%\Scripts\python.exe setup.py test -q
+On Windows:
+
+.. code-block:: doscon
+
+ c:\> %VENV%\Scripts\py.test -q
Here's sample output from a test run on UNIX:
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/python setup.py test -q
- running test
- running egg_info
- writing requirements to MyProject.egg-info/requires.txt
- writing MyProject.egg-info/PKG-INFO
- writing top-level names to MyProject.egg-info/top_level.txt
- writing dependency_links to MyProject.egg-info/dependency_links.txt
- writing entry points to MyProject.egg-info/entry_points.txt
- reading manifest file 'MyProject.egg-info/SOURCES.txt'
- writing manifest file 'MyProject.egg-info/SOURCES.txt'
- running build_ext
+ $ $VENV/bin/py.test -q
..
- ----------------------------------------------------------------------
- Ran 1 test in 0.108s
-
- OK
+ 2 passed in 0.47 seconds
The tests themselves are found in the ``tests.py`` module in your ``pcreate``
-generated project. Within a project generated by the ``starter`` scaffold, a
-single sample test exists.
+generated project. Within a project generated by the ``starter`` scaffold,
+only two sample tests exist.
.. note::
- The ``-q`` option is passed to the ``setup.py test`` command to limit the
- output to a stream of dots. If you don't pass ``-q``, you'll see more
- verbose test result output (which normally isn't very useful).
+ The ``-q`` option is passed to the ``py.test`` command to limit the output
+ to a stream of dots. If you don't pass ``-q``, you'll see verbose test
+ result output (which normally isn't very useful).
+
+Alternatively, if you'd like to see test coverage, pass the ``--cov`` option
+to ``py.test``:
+
+.. code-block:: bash
+
+ $ $VENV/bin/py.test --cov -q
+
+Scaffolds include configuration defaults for ``py.test`` and test coverage.
+These configuration files are ``pytest.ini`` and ``.coveragerc``, located at
+the root of your package. Without these defaults, we would need to specify the
+path to the module on which we want to run tests and coverage.
+
+.. code-block:: bash
+
+ $ $VENV/bin/py.test --cov=myproject myproject/tests.py -q
+
+.. seealso:: See py.test's documentation for :ref:`pytest:usage` or invoke
+ ``py.test -h`` to see its full set of options.
+
.. index::
single: running an application
@@ -250,13 +266,15 @@ single sample test exists.
Running the Project Application
-------------------------------
+.. seealso:: See also the output of :ref:`pserve --help <pserve_script>`.
+
Once a project is installed for development, you can run the application it
represents using the ``pserve`` command against the generated configuration
file. In our case, this file is named ``development.ini``.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/pserve development.ini
@@ -264,42 +282,42 @@ On Windows:
.. code-block:: text
- > %VENV%\Scripts\pserve development.ini
+ c:\> %VENV%\Scripts\pserve development.ini
Here's sample output from a run of ``pserve`` on UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/pserve development.ini
- Starting server in PID 16601.
- serving on http://0.0.0.0:6543
-
-When you use ``pserve`` to start the application implied by the default
-rendering of a scaffold, it will respond to requests on *all* IP addresses
-possessed by your system, not just requests to ``localhost``. This is what the
-``0.0.0.0`` in ``serving on http://0.0.0.0:6543`` means. The server will
-respond to requests made to ``127.0.0.1`` and on any external IP address. For
-example, your system might be configured to have an external IP address
-``192.168.1.50``. If that's the case, if you use a browser running on the same
-system as Pyramid, it will be able to access the application via
-``http://127.0.0.1:6543/`` as well as via ``http://192.168.1.50:6543/``.
-However, *other people* on other computers on the same network will also be
-able to visit your Pyramid application in their browser by visiting
-``http://192.168.1.50:6543/``.
-
-If you want to restrict access such that only a browser running on the same
-machine as Pyramid will be able to access your Pyramid application, edit the
+ Starting server in PID 16208.
+ serving on http://127.0.0.1:6543
+
+Access is restricted such that only a browser running on the same machine as
+Pyramid will be able to access your Pyramid application. However, if you want
+to open access to other machines on the same network, then edit the
``development.ini`` file, and replace the ``host`` value in the
-``[server:main]`` section. Change it from ``0.0.0.0`` to ``127.0.0.1``. For
+``[server:main]`` section, changing it from ``127.0.0.1`` to ``0.0.0.0``. For
example:
.. code-block:: ini
[server:main]
use = egg:waitress#main
- host = 127.0.0.1
+ host = 0.0.0.0
port = 6543
+Now when you use ``pserve`` to start the application, it will respond to
+requests on *all* IP addresses possessed by your system, not just requests to
+``localhost``. This is what the ``0.0.0.0`` in
+``serving on http://0.0.0.0:6543`` means. The server will respond to requests
+made to ``127.0.0.1`` and on any external IP address. For example, your system
+might be configured to have an external IP address ``192.168.1.50``. If that's
+the case, if you use a browser running on the same system as Pyramid, it will
+be able to access the application via ``http://127.0.0.1:6543/`` as well as via
+``http://192.168.1.50:6543/``. However, *other people* on other computers on
+the same network will also be able to visit your Pyramid application in their
+browser by visiting ``http://192.168.1.50:6543/``.
+
You can change the port on which the server runs on by changing the same
portion of the ``development.ini`` file. For example, you can change the
``port = 6543`` line in the ``development.ini`` file's ``[server:main]``
@@ -347,7 +365,7 @@ For example, on UNIX:
$ $VENV/bin/pserve development.ini --reload
Starting subprocess with file monitor
Starting server in PID 16601.
- serving on http://0.0.0.0:6543
+ serving on http://127.0.0.1:6543
Now if you make a change to any of your project's ``.py`` files or ``.ini``
files, you'll see the server restart automatically:
@@ -357,7 +375,7 @@ files, you'll see the server restart automatically:
development.ini changed; reloading...
-------------------- Restarting --------------------
Starting server in PID 16602.
- serving on http://0.0.0.0:6543
+ serving on http://127.0.0.1:6543
Changes to template files (such as ``.pt`` or ``.mak`` files) won't cause the
server to restart. Changes to template files don't require a server restart as
@@ -414,9 +432,8 @@ like this to enable the toolbar when your system contacts Pyramid:
# .. other settings ...
debugtoolbar.hosts = 192.168.1.1
-For more information about what the debug toolbar allows you to do, see `the
-documentation for pyramid_debugtoolbar
-<http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/>`_.
+For more information about what the debug toolbar allows you to do, see the
+:ref:`documentation for pyramid_debugtoolbar <toolbar:overview>`.
The debug toolbar will not be shown (and all debugging will be turned off) when
you use the ``production.ini`` file instead of the ``development.ini`` ini file
@@ -429,7 +446,7 @@ commenting out a line. For example, instead of:
:linenos:
[app:main]
- ...
+ # ... elided configuration
pyramid.includes =
pyramid_debugtoolbar
@@ -439,7 +456,7 @@ Put a hash mark at the beginning of the ``pyramid_debugtoolbar`` line:
:linenos:
[app:main]
- ...
+ # ... elided configuration
pyramid.includes =
# pyramid_debugtoolbar
@@ -579,18 +596,16 @@ file. The name ``main`` is a convention used by PasteDeploy signifying that it
is the default application.
The ``[server:main]`` section of the configuration file configures a WSGI
-server which listens on TCP port 6543. It is configured to listen on all
-interfaces (``0.0.0.0``). This means that any remote system which has TCP
-access to your system can see your Pyramid application.
+server which listens on TCP port 6543. It is configured to listen on localhost
+only (``127.0.0.1``).
.. _MyProject_ini_logging:
-The sections that live between the markers ``# Begin logging configuration``
-and ``# End logging configuration`` represent Python's standard library
-:mod:`logging` module configuration for your application. The sections between
-these two markers are passed to the `logging module's config file configuration
-engine <http://docs.python.org/howto/logging.html#configuring-logging>`_ when
-the ``pserve`` or ``pshell`` commands are executed. The default configuration
+The sections after ``# logging configuration`` represent Python's standard
+library :mod:`logging` module configuration for your application. These
+sections are passed to the `logging module's config file configuration engine
+<https://docs.python.org/2/howto/logging.html#configuring-logging>`_ when the
+``pserve`` or ``pshell`` commands are executed. The default configuration
sends application logging output to the standard error output of your terminal.
For more information about logging configuration, see :ref:`logging_chapter`.
@@ -633,8 +648,8 @@ setup.py sdist``. Due to the information contained in the default
``MANIFEST.in``, an sdist of your Pyramid project will include ``.txt`` files,
``.ini`` files, ``.rst`` files, graphics files, and template files, as well as
``.py`` files. See
-http://docs.python.org/distutils/sourcedist.html#the-manifest-in-template for
-more information about the syntax and usage of ``MANIFEST.in``.
+https://docs.python.org/2/distutils/sourcedist.html#the-manifest-in-template
+for more information about the syntax and usage of ``MANIFEST.in``.
Without the presence of a ``MANIFEST.in`` file or without checking your source
code into a version control repository, ``setup.py sdist`` places only *Python
@@ -652,8 +667,8 @@ files with extensions other than the files named in the project's
``MANIFEST.in`` and you don't make use of a setuptools-compatible version
control system, you'll need to edit the ``MANIFEST.in`` file and include the
statements necessary to include your new files. See
-http://docs.python.org/distutils/sourcedist.html#principle for more information
-about how to do this.
+https://docs.python.org/2/distutils/sourcedist.html#principle for more
+information about how to do this.
You can also delete ``MANIFEST.in`` from your project and rely on a setuptools
feature which simply causes all files checked into a version control system to
@@ -672,16 +687,16 @@ control system, you may need to install a setuptools add-on such as
~~~~~~~~~~~~
The ``setup.py`` file is a :term:`setuptools` setup file. It is meant to be
-run directly from the command line to perform a variety of functions, such as
-testing, packaging, and distributing your application.
+used to define requirements for installing dependencies for your package and
+testing, as well as distributing your application.
.. note::
``setup.py`` is the de facto standard which Python developers use to
distribute their reusable code. You can read more about ``setup.py`` files
- and their usage in the `Setuptools documentation
- <http://peak.telecommunity.com/DevCenter/setuptools>`_ and `The Hitchhiker's
- Guide to Packaging <http://guide.python-distribute.org/>`_.
+ and their usage in the `Python Packaging User Guide
+ <https://packaging.python.org/en/latest/>`_ and `Setuptools documentation
+ <http://pythonhosted.org/setuptools/>`_.
Our generated ``setup.py`` looks like this:
@@ -690,7 +705,7 @@ Our generated ``setup.py`` looks like this:
:linenos:
The ``setup.py`` file calls the setuptools ``setup`` function, which does
-various things depending on the arguments passed to ``setup.py`` on the command
+various things depending on the arguments passed to ``pip`` on the command
line.
Within the arguments to this function call, information about your application
@@ -701,23 +716,22 @@ exists in this file in this section.
Your application's name can be any string; it is specified in the ``name``
field. The version number is specified in the ``version`` value. A short
description is provided in the ``description`` field. The ``long_description``
-is conventionally the content of the README and CHANGES file appended together.
-The ``classifiers`` field is a list of `Trove
-<http://pypi.python.org/pypi?%3Aaction=list_classifiers>`_ classifiers
-describing your application. ``author`` and ``author_email`` are text fields
-which probably don't need any description. ``url`` is a field that should
-point at your application project's URL (if any). ``packages=find_packages()``
-causes all packages within the project to be found when packaging the
-application. ``include_package_data`` will include non-Python files when the
-application is packaged if those files are checked into version control.
-``zip_safe`` indicates that this package is not safe to use as a zipped egg;
-instead it will always unpack as a directory, which is more convenient.
-``install_requires`` and ``tests_require`` indicate that this package depends
-on the ``pyramid`` package. ``test_suite`` points at the package for our
-application, which means all tests found in the package will be run when
-``setup.py test`` is invoked. We examined ``entry_points`` in our discussion
-of the ``development.ini`` file; this file defines the ``main`` entry point
-that represents our project's application.
+is conventionally the content of the ``README`` and ``CHANGES`` files appended
+together. The ``classifiers`` field is a list of `Trove classifiers
+<https://pypi.python.org/pypi?%3Aaction=list_classifiers>`_ describing your
+application. ``author`` and ``author_email`` are text fields which probably
+don't need any description. ``url`` is a field that should point at your
+application project's URL (if any). ``packages=find_packages()`` causes all
+packages within the project to be found when packaging the application.
+``include_package_data`` will include non-Python files when the application is
+packaged if those files are checked into version control. ``zip_safe=False``
+indicates that this package is not safe to use as a zipped egg; instead it will
+always unpack as a directory, which is more convenient. ``install_requires``
+indicates that this package depends on the ``pyramid`` package.
+``extras_require`` is a Python dictionary that defines what is required to be
+installed for running tests. We examined ``entry_points`` in our discussion of
+the ``development.ini`` file; this file defines the ``main`` entry point that
+represents our project's application.
Usually you only need to think about the contents of the ``setup.py`` file when
distributing your application to other people, when adding Python package
@@ -729,7 +743,7 @@ you can try this command now:
$ $VENV/bin/python setup.py sdist
This will create a tarball of your application in a ``dist`` subdirectory named
-``MyProject-0.1.tar.gz``. You can send this tarball to other people who want
+``MyProject-0.0.tar.gz``. You can send this tarball to other people who want
to install and use your application.
.. index::
@@ -912,13 +926,13 @@ The ``tests.py`` module includes unit tests for your application.
.. literalinclude:: MyProject/myproject/tests.py
:language: python
- :lines: 1-18
:linenos:
-This sample ``tests.py`` file has a single unit test defined within it. This
-test is executed when you run ``python setup.py test``. You may add more tests
-here as you build your application. You are not required to write tests to use
-:app:`Pyramid`. This file is simply provided for convenience and example.
+This sample ``tests.py`` file has one unit test and one functional test defined
+within it. These tests are executed when you run ``py.test -q``. You may add
+more tests here as you build your application. You are not required to write
+tests to use :app:`Pyramid`. This file is simply provided for convenience and
+example.
See :ref:`testing_chapter` for more information about writing :app:`Pyramid`
unit tests.
diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst
index 50e85813a..e06c78028 100644
--- a/docs/narr/renderers.rst
+++ b/docs/narr/renderers.rst
@@ -317,7 +317,7 @@ JSONP Renderer
.. versionadded:: 1.1
:class:`pyramid.renderers.JSONP` is a `JSONP
-<http://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper which implements
+<https://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper which implements
a hybrid JSON/JSONP renderer. JSONP is useful for making cross-domain AJAX
requests.
diff --git a/docs/narr/router.rst b/docs/narr/router.rst
index e02142e6e..e45e6f4a8 100644
--- a/docs/narr/router.rst
+++ b/docs/narr/router.rst
@@ -41,19 +41,24 @@ request enters a :app:`Pyramid` application through to the point that
user-defined :term:`route` matches the current WSGI environment. The
:term:`router` passes the request as an argument to the mapper.
-#. If any route matches, the route mapper adds attributes to the request:
- ``matchdict`` and ``matched_route`` attributes are added to the request
- object. The former contains a dictionary representing the matched dynamic
- elements of the request's ``PATH_INFO`` value, and the latter contains the
+#. If any route matches, the route mapper adds the attributes ``matchdict``
+ and ``matched_route`` to the request object. The former contains a
+ dictionary representing the matched dynamic elements of the request's
+ ``PATH_INFO`` value, and the latter contains the
:class:`~pyramid.interfaces.IRoute` object representing the route which
- matched. The root object associated with the route found is also generated:
- if the :term:`route configuration` which matched has an associated
- ``factory`` argument, this factory is used to generate the root object,
- otherwise a default :term:`root factory` is used.
+ matched.
-#. If a route match was *not* found, and a ``root_factory`` argument was passed
+#. A :class:`~pyramid.events.BeforeTraversal` :term:`event` is sent to any
+ subscribers.
+
+#. Continuing, if any route matches, the root object associated with the found
+ route is generated. If the :term:`route configuration` which matched has an
+ associated ``factory`` argument, then this factory is used to generate the
+ root object; otherwise a default :term:`root factory` is used.
+
+ However, if no route matches, and if a ``root_factory`` argument was passed
to the :term:`Configurator` constructor, that callable is used to generate
- the root object. If the ``root_factory`` argument passed to the
+ the root object. If the ``root_factory`` argument passed to the
Configurator constructor was ``None``, a default root factory is used to
generate a root object.
diff --git a/docs/narr/scaffolding.rst b/docs/narr/scaffolding.rst
index 8677359de..164ceb3bf 100644
--- a/docs/narr/scaffolding.rst
+++ b/docs/narr/scaffolding.rst
@@ -22,10 +22,10 @@ found by the ``pcreate`` command.
To create a scaffold template, create a Python :term:`distribution` to house
the scaffold which includes a ``setup.py`` that relies on the ``setuptools``
-package. See `Creating a Package
-<http://guide.python-distribute.org/creation.html>`_ for more information about
-how to do this. For example, we'll pretend the distribution you create is
-named ``CoolExtension``, and it has a package directory within it named
+package. See `Packaging and Distributing Projects
+<https://packaging.python.org/en/latest/distributing/>`_ for more information
+about how to do this. For example, we'll pretend the distribution you create
+is named ``CoolExtension``, and it has a package directory within it named
``coolextension``.
Once you've created the distribution, put a "scaffolds" directory within your
diff --git a/docs/narr/security.rst b/docs/narr/security.rst
index 7cbea113c..77e7fd707 100644
--- a/docs/narr/security.rst
+++ b/docs/narr/security.rst
@@ -290,6 +290,14 @@ properties of the instance.
def __init__(self, owner):
self.owner = owner
+.. warning::
+
+ Writing ``__acl__`` as properties is discouraged because an
+ ``AttributeError`` occurring in ``fget`` or ``fset`` will be silently
+ dismissed (this is consistent with Python ``getattr`` and ``hasattr``
+ behaviors). For dynamic ACLs, simply use callables, as documented above.
+
+
.. index::
single: ACE
single: access control entry
diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst
index db554a93b..a1319e45f 100644
--- a/docs/narr/sessions.rst
+++ b/docs/narr/sessions.rst
@@ -260,19 +260,28 @@ added to the flash queue, and empties the queue.
.. method:: pop_flash(queue='')
->>> request.session.flash('info message')
->>> request.session.pop_flash()
-['info message']
+.. testsetup::
+
+ from pyramid import testing
+ request = testing.DummyRequest()
+
+.. doctest::
+
+ >>> request.session.flash('info message')
+ >>> request.session.pop_flash()
+ ['info message']
Calling ``session.pop_flash()`` again like above without a corresponding call
to ``session.flash()`` will return an empty list, because the queue has already
been popped.
->>> request.session.flash('info message')
->>> request.session.pop_flash()
-['info message']
->>> request.session.pop_flash()
-[]
+.. doctest::
+
+ >>> request.session.flash('info message')
+ >>> request.session.pop_flash()
+ ['info message']
+ >>> request.session.pop_flash()
+ []
.. index::
single: session.peek_flash
@@ -287,15 +296,17 @@ flash storage.
.. method:: peek_flash(queue='')
->>> request.session.flash('info message')
->>> request.session.peek_flash()
-['info message']
->>> request.session.peek_flash()
-['info message']
->>> request.session.pop_flash()
-['info message']
->>> request.session.peek_flash()
-[]
+.. doctest::
+
+ >>> request.session.flash('info message')
+ >>> request.session.peek_flash()
+ ['info message']
+ >>> request.session.peek_flash()
+ ['info message']
+ >>> request.session.pop_flash()
+ ['info message']
+ >>> request.session.peek_flash()
+ []
.. index::
single: preventing cross-site request forgery attacks
@@ -305,7 +316,7 @@ Preventing Cross-Site Request Forgery Attacks
---------------------------------------------
`Cross-site request forgery
-<http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a
+<https://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a
phenomenon whereby a user who is logged in to your website might inadvertantly
load a URL because it is linked from, or embedded in, an attacker's website.
If the URL is one that may modify or delete data, the consequences can be dire.
@@ -367,6 +378,21 @@ Or include it as a header in a jQuery AJAX request:
The handler for the URL that receives the request should then require that the
correct CSRF token is supplied.
+.. index::
+ single: session.new_csrf_token
+
+Using the ``session.new_csrf_token`` Method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To explicitly create a new CSRF token, use the ``session.new_csrf_token()``
+method. This differs only from ``session.get_csrf_token()`` inasmuch as it
+clears any existing CSRF token, creates a new CSRF token, sets the token into
+the session, and returns the token.
+
+.. code-block:: python
+
+ token = request.session.new_csrf_token()
+
Checking CSRF Tokens Manually
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -376,25 +402,76 @@ will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally,
you can specify ``raises=False`` to have the check return ``False`` instead of
raising an exception.
-By default, it checks for a GET or POST parameter named ``csrf_token`` or a
-header named ``X-CSRF-Token``.
+By default, it checks for a POST parameter named ``csrf_token`` or a header
+named ``X-CSRF-Token``.
.. code-block:: python
- from pyramid.session import check_csrf_token
+ from pyramid.session import check_csrf_token
- def myview(request):
- # Require CSRF Token
- check_csrf_token(request)
+ def myview(request):
+ # Require CSRF Token
+ check_csrf_token(request)
- # ...
+ # ...
-.. index::
- single: session.new_csrf_token
+.. _auto_csrf_checking:
+
+Checking CSRF Tokens Automatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 1.7
+
+:app:`Pyramid` supports automatically checking CSRF tokens on requests with an
+unsafe method as defined by RFC2616. Any other request may be checked manually.
+This feature can be turned on globally for an application using the
+:meth:`pyramid.config.Configurator.set_default_csrf_options` directive.
+For example:
+
+.. code-block:: python
+
+ from pyramid.config import Configurator
+
+ config = Configurator()
+ config.set_default_csrf_options(require_csrf=True)
+
+CSRF checking may be explicitly enabled or disabled on a per-view basis using
+the ``require_csrf`` view option. A value of ``True`` or ``False`` will
+override the default set by ``set_default_csrf_options``. For example:
+
+.. code-block:: python
+
+ @view_config(route_name='hello', require_csrf=False)
+ def myview(request):
+ # ...
+
+When CSRF checking is active, the token and header used to find the
+supplied CSRF token will be ``csrf_token`` and ``X-CSRF-Token``, respectively,
+unless otherwise overridden by ``set_default_csrf_options``. The token is
+checked against the value in ``request.POST`` which is the submitted form body.
+If this value is not present, then the header will be checked.
+
+In addition to token based CSRF checks, if the request is using HTTPS then the
+automatic CSRF checking will also check the referrer of the request to ensure
+that it matches one of the trusted origins. By default the only trusted origin
+is the current host, however additional origins may be configured by setting
+``pyramid.csrf_trusted_origins`` to a list of domain names (and ports if they
+are non standard). If a host in the list of domains starts with a ``.`` then
+that will allow all subdomains as well as the domain without the ``.``.
+
+If CSRF checks fail then a :class:`pyramid.exceptions.BadCSRFToken` or
+:class:`pyramid.exceptions.BadCSRFOrigin` exception will be raised. This
+exception may be caught and handled by an :term:`exception view` but, by
+default, will result in a ``400 Bad Request`` response being sent to the
+client.
Checking CSRF Tokens with a View Predicate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. deprecated:: 1.7
+ Use the ``require_csrf`` option or read :ref:`auto_csrf_checking` instead
+ to have :class:`pyramid.exceptions.BadCSRFToken` exceptions raised.
+
A convenient way to require a valid CSRF token for a particular view is to
include ``check_csrf=True`` as a view predicate. See
:meth:`pyramid.config.Configurator.add_view`.
@@ -410,15 +487,3 @@ include ``check_csrf=True`` as a view predicate. See
predicate system, when it doesn't find a view, raises ``HTTPNotFound``
instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different
from calling :func:`pyramid.session.check_csrf_token`.
-
-Using the ``session.new_csrf_token`` Method
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To explicitly create a new CSRF token, use the ``session.new_csrf_token()``
-method. This differs only from ``session.get_csrf_token()`` inasmuch as it
-clears any existing CSRF token, creates a new CSRF token, sets the token into
-the session, and returns the token.
-
-.. code-block:: python
-
- token = request.session.new_csrf_token()
diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst
index 485f6b181..3e168eaea 100644
--- a/docs/narr/startup.rst
+++ b/docs/narr/startup.rst
@@ -6,15 +6,15 @@ Startup
When you cause a :app:`Pyramid` application to start up in a console window,
you'll see something much like this show up on the console:
-.. code-block:: text
+.. code-block:: bash
- $ pserve development.ini
- Starting server in PID 16601.
- serving on 0.0.0.0:6543 view at http://127.0.0.1:6543
+ $ $VENV/bin/pserve development.ini
+ Starting server in PID 16305.
+ serving on http://127.0.0.1:6543
This chapter explains what happens between the time you press the "Return" key
on your keyboard after typing ``pserve development.ini`` and the time the line
-``serving on 0.0.0.0:6543 ...`` is output to your console.
+``serving on http://127.0.0.1:6543`` is output to your console.
.. index::
single: startup process
@@ -92,11 +92,11 @@ Here's a high-level time-ordered overview of what happens when you press
In this case, the ``myproject.__init__:main`` function referred to by the
entry point URI ``egg:MyProject`` (see :ref:`MyProject_ini` for more
information about entry point URIs, and how they relate to callables) will
- receive the key/value pairs ``{'pyramid.reload_templates':'true',
- 'pyramid.debug_authorization':'false', 'pyramid.debug_notfound':'false',
- 'pyramid.debug_routematch':'false', 'pyramid.debug_templates':'true',
- 'pyramid.default_locale_name':'en'}``. See :ref:`environment_chapter` for
- the meanings of these keys.
+ receive the key/value pairs ``{pyramid.reload_templates = true,
+ pyramid.debug_authorization = false, pyramid.debug_notfound = false,
+ pyramid.debug_routematch = false, pyramid.default_locale_name = en, and
+ pyramid.includes = pyramid_debugtoolbar}``. See :ref:`environment_chapter`
+ for the meanings of these keys.
#. The ``main`` function first constructs a
:class:`~pyramid.config.Configurator` instance, passing the ``settings``
@@ -131,10 +131,9 @@ Here's a high-level time-ordered overview of what happens when you press
#. ``pserve`` starts the WSGI *server* defined within the ``[server:main]``
section. In our case, this is the Waitress server (``use =
egg:waitress#main``), and it will listen on all interfaces (``host =
- 0.0.0.0``), on port number 6543 (``port = 6543``). The server code itself
- is what prints ``serving on 0.0.0.0:6543 view at http://127.0.0.1:6543``.
- The server serves the application, and the application is running, waiting
- to receive requests.
+ 127.0.0.1``), on port number 6543 (``port = 6543``). The server code itself
+ is what prints ``serving on http://127.0.0.1:6543``. The server serves the
+ application, and the application is running, waiting to receive requests.
.. seealso::
Logging configuration is described in the :ref:`logging_chapter` chapter.
diff --git a/docs/narr/subrequest.rst b/docs/narr/subrequest.rst
index 02ae14aa5..7c847de50 100644
--- a/docs/narr/subrequest.rst
+++ b/docs/narr/subrequest.rst
@@ -17,7 +17,7 @@ application.
Here's an example application which uses a subrequest:
.. code-block:: python
- :linenos:
+ :linenos:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
@@ -61,8 +61,8 @@ adapter when found and invoked via
object:
.. code-block:: python
- :linenos:
- :emphasize-lines: 11
+ :linenos:
+ :emphasize-lines: 11
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
@@ -106,8 +106,8 @@ exception, the exception will be raised to the caller of
:term:`exception view` configured:
.. code-block:: python
- :linenos:
- :emphasize-lines: 11-16
+ :linenos:
+ :emphasize-lines: 11-16
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
@@ -175,8 +175,8 @@ We can cause the subrequest to be run through the tween stack by passing
:meth:`~pyramid.request.Request.invoke_subrequest`, like this:
.. code-block:: python
- :linenos:
- :emphasize-lines: 7
+ :linenos:
+ :emphasize-lines: 7
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
@@ -279,3 +279,53 @@ within a tween, because tweens already, by definition, have access to a
function that will cause a subrequest (they are passed a ``handle`` function).
It's fine to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from
within an event handler, however.
+
+
+.. index::
+ pair: subrequest; exception view
+
+Invoking an Exception View
+--------------------------
+
+.. versionadded:: 1.7
+
+:app:`Pyramid` apps may define :term:`exception views <exception view>` which
+can handle any raised exceptions that escape from your code while processing
+a request. By default an unhandled exception will be caught by the ``EXCVIEW``
+:term:`tween`, which will then lookup an exception view that can handle the
+exception type, generating an appropriate error response.
+
+In :app:`Pyramid` 1.7 the :meth:`pyramid.request.Request.invoke_exception_view`
+was introduced, allowing a user to invoke an exception view while manually
+handling an exception. This can be useful in a few different circumstances:
+
+- Manually handling an exception losing the current call stack or flow.
+
+- Handling exceptions outside of the context of the ``EXCVIEW`` tween. The
+ tween only covers certain parts of the request processing pipeline (See
+ :ref:`router_chapter`). There are also some corner cases where an exception
+ can be raised that will still bubble up to middleware, and possibly to the
+ web server in which case a generic ``500 Internal Server Error`` will be
+ returned to the client.
+
+Below is an example usage of
+:meth:`pyramid.request.Request.invoke_exception_view`:
+
+.. code-block:: python
+ :linenos:
+
+ def foo(request):
+ try:
+ some_func_that_errors()
+ return response
+ except Exception:
+ response = request.invoke_exception_view()
+ if response is not None:
+ return response
+ else:
+ # there is no exception view for this exception, simply
+ # re-raise and let someone else handle it
+ raise
+
+Please note that in most cases you do not need to write code like this, and you
+may rely on the ``EXCVIEW`` tween to handle this for you.
diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst
index 9e3a31845..6b3b5fcce 100644
--- a/docs/narr/templates.rst
+++ b/docs/narr/templates.rst
@@ -448,7 +448,7 @@ templating languages including the following:
.. _pyramid_chameleon:
http://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
-.. _Jinja2: http://jinja.pocoo.org/docs/
+.. _Jinja2: http://jinja.pocoo.org/docs/dev/
.. _pyramid_jinja2:
http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/
diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst
index c05ee41ad..354a462d4 100644
--- a/docs/narr/testing.rst
+++ b/docs/narr/testing.rst
@@ -275,7 +275,7 @@ without needing to invoke the actual application configuration implied by its
In the above example, we create a ``MyTest`` test case that inherits from
:class:`unittest.TestCase`. If it's in our :app:`Pyramid` application, it will
-be found when ``setup.py test`` is run. It has two test methods.
+be found when ``py.test`` is run. It has two test methods.
The first test method, ``test_view_fn_forbidden`` tests the ``view_fn`` when
the authentication policy forbids the current user the ``edit`` permission. Its
@@ -348,26 +348,6 @@ code's integration with the rest of :app:`Pyramid`.
See also :ref:`including_configuration`
-Let's demonstrate this by showing an integration test for a view.
-
-Given the following view definition, which assumes that your application's
-:term:`package` name is ``myproject``, and within that :term:`package` there
-exists a module ``views``, which in turn contains a :term:`view` function named
-``my_view``:
-
- .. literalinclude:: MyProject/myproject/views.py
- :linenos:
- :lines: 1-6
- :language: python
-
-You'd then create a ``tests`` module within your ``myproject`` package,
-containing the following test code:
-
- .. literalinclude:: MyProject/myproject/tests.py
- :linenos:
- :pyobject: ViewIntegrationTests
- :language: python
-
Writing unit tests that use the :class:`~pyramid.config.Configurator` API to
set up the right "mock" registrations is often preferred to creating
integration tests. Unit tests will run faster (because they do less for each
@@ -385,25 +365,47 @@ Functional tests test your literal application.
In Pyramid, functional tests are typically written using the :term:`WebTest`
package, which provides APIs for invoking HTTP(S) requests to your application.
+We also like ``py.test`` and ``pytest-cov`` to provide simple testing and
+coverage reports.
-Regardless of which testing :term:`package` you use, ensure to add a
-``tests_require`` dependency on that package to your application's
-``setup.py`` file:
+Regardless of which testing :term:`package` you use, be sure to add a
+``tests_require`` dependency on that package to your application's ``setup.py``
+file. Using the project ``MyProject`` generated by the starter scaffold as
+described in :doc:`project`, we would insert the following code immediately
+following the ``requires`` block in the file ``MyProject/setup.py``.
- .. literalinclude:: MyProject/setup.py
- :linenos:
- :emphasize-lines: 26-28,48
- :language: python
+.. literalinclude:: MyProject/setup.py
+ :language: python
+ :linenos:
+ :lines: 11-22
+ :lineno-start: 11
+ :emphasize-lines: 8-
+
+Remember to change the dependency.
+
+.. literalinclude:: MyProject/setup.py
+ :language: python
+ :linenos:
+ :lines: 40-44
+ :lineno-start: 40
+ :emphasize-lines: 2-4
+
+As always, whenever you change your dependencies, make sure to run the correct
+``pip install -e`` command.
+
+.. code-block:: bash
+
+ $VENV/bin/pip install -e ".[testing]"
-Let us assume your :term:`package` is named ``myproject`` which contains a
-``views`` module, which in turn contains a :term:`view` function ``my_view``
-that returns a HTML body when the root URL is invoked:
+In your ``MyPackage`` project, your :term:`package` is named ``myproject``
+which contains a ``views`` module, which in turn contains a :term:`view`
+function ``my_view`` that returns an HTML body when the root URL is invoked:
.. literalinclude:: MyProject/myproject/views.py
:linenos:
:language: python
-Then the following example functional test demonstrates invoking the above
+The following example functional test demonstrates invoking the above
:term:`view`:
.. literalinclude:: MyProject/myproject/tests.py
@@ -414,9 +416,9 @@ Then the following example functional test demonstrates invoking the above
When this test is run, each test method creates a "real" :term:`WSGI`
application using the ``main`` function in your ``myproject.__init__`` module,
using :term:`WebTest` to wrap that WSGI application. It assigns the result to
-``self.testapp``. In the test named ``test_root``. The ``TestApp``'s ``GET``
+``self.testapp``. In the test named ``test_root``, the ``TestApp``'s ``GET``
method is used to invoke the root URL. Finally, an assertion is made that the
-returned HTML contains the text ``MyProject``.
+returned HTML contains the text ``Pyramid``.
See the :term:`WebTest` documentation for further information about the methods
available to a :class:`webtest.app.TestApp` instance.
diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst
index db9b5e090..4e434c3c6 100644
--- a/docs/narr/upgrading.rst
+++ b/docs/narr/upgrading.rst
@@ -75,6 +75,27 @@ changes are noted in the :ref:`changelog`, so it's possible to know that you
should change older spellings to newer ones to ensure that people reading your
code can find the APIs you're using in the Pyramid docs.
+
+Python support policy
+~~~~~~~~~~~~~~~~~~~~~
+
+At the time of a Pyramid version release, each supports all versions of Python
+through the end of their lifespans. The end-of-life for a given version of
+Python is when security updates are no longer released.
+
+- `Python 3.2 Lifespan <https://www.python.org/dev/peps/pep-0392/#lifespan>`_
+ ends February 2016.
+- `Python 3.3 Lifespan <https://www.python.org/dev/peps/pep-0392/#lifespan>`_
+ ends September 2017.
+- `Python 3.4 Lifespan <https://www.python.org/dev/peps/pep-0429/>`_ TBD.
+- `Python 3.5 Lifespan <https://www.python.org/dev/peps/pep-0478/>`_ TBD.
+- `Python 3.6 Lifespan <https://www.python.org/dev/peps/pep-0494/#id4>`_
+ December 2021.
+
+To determine the Python support for a specific release of Pyramid, view its
+``tox.ini`` file at the root of the repository's version.
+
+
Consulting the change history
-----------------------------
@@ -106,10 +127,9 @@ you can see DeprecationWarnings printed to the console when the tests run.
$ python -Wd setup.py test -q
The ``-Wd`` argument tells Python to print deprecation warnings to the console.
-Note that the ``-Wd`` flag is only required for Python 2.7 and better: Python
-versions 2.6 and older print deprecation warnings to the console by default.
See `the Python -W flag documentation
-<http://docs.python.org/using/cmdline.html#cmdoption-W>`_ for more information.
+<https://docs.python.org/2/using/cmdline.html#cmdoption-W>`_ for more
+information.
As your tests run, deprecation warnings will be printed to the console
explaining the deprecation and providing instructions about how to prevent the
@@ -185,10 +205,10 @@ On UNIX, you can do that via:
On Windows, you need to issue two commands:
-.. code-block:: bash
+.. code-block:: doscon
- C:\> set PYTHONWARNINGS=default
- C:\> Scripts/pserve.exe development.ini
+ c:\> set PYTHONWARNINGS=default
+ c:\> Scripts/pserve.exe development.ini
At this point, it's ensured that deprecation warnings will be printed to the
console whenever a codepath is hit that generates one. You can then click
@@ -196,9 +216,10 @@ around in your application interactively to try to generate them, and remediate
as explained in :ref:`testing_under_new_release`.
See `the PYTHONWARNINGS environment variable documentation
-<http://docs.python.org/using/cmdline.html#envvar-PYTHONWARNINGS>`_ or `the
+<https://docs.python.org/2/using/cmdline.html#envvar-PYTHONWARNINGS>`_ or `the
Python -W flag documentation
-<http://docs.python.org/using/cmdline.html#cmdoption-W>`_ for more information.
+<https://docs.python.org/2/using/cmdline.html#cmdoption-W>`_ for more
+information.
Upgrading to the very latest Pyramid release
--------------------------------------------
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index c13558008..7d37c04df 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -271,8 +271,9 @@ pattern like this:
But this will either cause an error at startup time or it won't match properly.
You'll want to use a Unicode value as the pattern instead rather than raw
bytestring escapes. You can use a high-order Unicode value as the pattern by
-using `Python source file encoding <http://www.python.org/dev/peps/pep-0263/>`_
-plus the "real" character in the Unicode pattern in the source, like so:
+using `Python source file encoding
+<https://www.python.org/dev/peps/pep-0263/>`_ plus the "real" character in the
+Unicode pattern in the source, like so:
.. code-block:: text
@@ -556,7 +557,7 @@ Here is an example of a corresponding ``mypackage.views`` module:
@view_config(route_name='idea')
def idea_view(request):
- return Response(request.matchdict['id'])
+ return Response(request.matchdict['idea'])
@view_config(route_name='user')
def user_view(request):
@@ -1194,7 +1195,7 @@ If a predicate is a class, just add ``__text__`` property in a standard manner.
__text__ = 'my custom class predicate'
If a predicate is a method, you'll need to assign it after method declaration
-(see `PEP 232 <http://www.python.org/dev/peps/pep-0232/>`_).
+(see `PEP 232 <https://www.python.org/dev/peps/pep-0232/>`_).
.. code-block:: python
:linenos:
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 0bd52b6e2..cd5b8feb0 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -192,6 +192,36 @@ Non-Predicate Arguments
only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with
the first element of ``None``, i.e., ``(None, {'public':True})``.
+
+``require_csrf``
+
+ CSRF checks will affect any request method that is not defined as a "safe"
+ method by RFC2616. In pratice this means that GET, HEAD, OPTIONS, and TRACE
+ methods will pass untouched and all others methods will require CSRF. This
+ option is used in combination with the ``pyramid.require_default_csrf``
+ setting to control which request parameters are checked for CSRF tokens.
+
+ This feature requires a configured :term:`session factory`.
+
+ If this option is set to ``True`` then CSRF checks will be enabled for POST
+ requests to this view. The required token will be whatever was specified by
+ the ``pyramid.require_default_csrf`` setting, or will fallback to
+ ``csrf_token``.
+
+ If this option is set to a string then CSRF checks will be enabled and it
+ will be used as the required token regardless of the
+ ``pyramid.require_default_csrf`` setting.
+
+ If this option is set to ``False`` then CSRF checks will be disabled
+ regardless of the ``pyramid.require_default_csrf`` setting.
+
+ In addition, if this option is set to ``True`` or a string then CSRF origin
+ checking will be enabled.
+
+ See :ref:`auto_csrf_checking` for more information.
+
+ .. versionadded:: 1.7
+
``wrapper``
The :term:`view name` of a different :term:`view configuration` which will
receive the response body of this view as the ``request.wrapped_body``
@@ -433,7 +463,7 @@ configured view.
check name.
If CSRF checking is performed, the checked value will be the value of
- ``request.params[check_name]``. This value will be compared against the
+ ``request.POST[check_name]``. This value will be compared against the
value of ``request.session.get_csrf_token()``, and the check will pass if
these two values are the same. If the check passes, the associated view will
be permitted to execute. If the check fails, the associated view will not be
diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst
index f18cf1dfb..ce1586834 100644
--- a/docs/narr/webob.rst
+++ b/docs/narr/webob.rst
@@ -27,8 +27,8 @@ functionality to the standard WebOb request, which is documented in the
:ref:`request_module` API documentation.
WebOb provides objects for HTTP requests and responses. Specifically it does
-this by wrapping the `WSGI <http://wsgi.org>`_ request environment and response
-status, header list, and app_iter (body) values.
+this by wrapping the `WSGI <http://wsgi.readthedocs.org/en/latest/>`_ request
+environment and response status, header list, and app_iter (body) values.
WebOb request and response objects provide many conveniences for parsing WSGI
requests and forming WSGI responses. WebOb is a nice way to represent "raw"
@@ -46,7 +46,7 @@ Request
~~~~~~~
The request object is a wrapper around the `WSGI environ dictionary
-<http://www.python.org/dev/peps/pep-0333/#environ-variables>`_. This
+<https://www.python.org/dev/peps/pep-0333/#environ-variables>`_. This
dictionary contains keys for each header, keys that describe the request
(including the path and query string), a file-like object for the request body,
and a variety of custom keys. You can always access the environ with
@@ -269,7 +269,7 @@ to a :app:`Pyramid` application:
When such a request reaches a view in your application, the
``request.json_body`` attribute will be available in the view callable body.
-.. code-block:: javascript
+.. code-block:: python
@view_config(renderer='string')
def aview(request):
diff --git a/docs/pscripts/index.rst b/docs/pscripts/index.rst
new file mode 100644
index 000000000..857e0564f
--- /dev/null
+++ b/docs/pscripts/index.rst
@@ -0,0 +1,12 @@
+.. _pscripts_documentation:
+
+``p*`` Scripts Documentation
+============================
+
+Command line programs (``p*`` scripts) included with :app:`Pyramid`.
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ *
diff --git a/docs/pscripts/pcreate.rst b/docs/pscripts/pcreate.rst
new file mode 100644
index 000000000..b5ec3f4e2
--- /dev/null
+++ b/docs/pscripts/pcreate.rst
@@ -0,0 +1,13 @@
+.. index::
+ single: pcreate; --help
+
+.. _pcreate_script:
+
+``pcreate``
+-----------
+
+.. program-output:: pcreate --help
+ :prompt:
+ :shell:
+
+.. seealso:: :ref:`creating_a_project`
diff --git a/docs/pscripts/pdistreport.rst b/docs/pscripts/pdistreport.rst
new file mode 100644
index 000000000..1c53fb6e9
--- /dev/null
+++ b/docs/pscripts/pdistreport.rst
@@ -0,0 +1,13 @@
+.. index::
+ single: pdistreport; --help
+
+.. _pdistreport_script:
+
+``pdistreport``
+---------------
+
+.. program-output:: pdistreport --help
+ :prompt:
+ :shell:
+
+.. seealso:: :ref:`showing_distributions`
diff --git a/docs/pscripts/prequest.rst b/docs/pscripts/prequest.rst
new file mode 100644
index 000000000..a15827767
--- /dev/null
+++ b/docs/pscripts/prequest.rst
@@ -0,0 +1,13 @@
+.. index::
+ single: prequest; --help
+
+.. _prequest_script:
+
+``prequest``
+------------
+
+.. program-output:: prequest --help
+ :prompt:
+ :shell:
+
+.. seealso:: :ref:`invoking_a_request`
diff --git a/docs/pscripts/proutes.rst b/docs/pscripts/proutes.rst
new file mode 100644
index 000000000..09ed013e1
--- /dev/null
+++ b/docs/pscripts/proutes.rst
@@ -0,0 +1,13 @@
+.. index::
+ single: proutes; --help
+
+.. _proutes_script:
+
+``proutes``
+-----------
+
+.. program-output:: proutes --help
+ :prompt:
+ :shell:
+
+.. seealso:: :ref:`displaying_application_routes`
diff --git a/docs/pscripts/pserve.rst b/docs/pscripts/pserve.rst
new file mode 100644
index 000000000..d33d4a484
--- /dev/null
+++ b/docs/pscripts/pserve.rst
@@ -0,0 +1,13 @@
+.. index::
+ single: pserve; --help
+
+.. _pserve_script:
+
+``pserve``
+----------
+
+.. program-output:: pserve --help
+ :prompt:
+ :shell:
+
+.. seealso:: :ref:`running_the_project_application`
diff --git a/docs/pscripts/pshell.rst b/docs/pscripts/pshell.rst
new file mode 100644
index 000000000..cfd84d4f8
--- /dev/null
+++ b/docs/pscripts/pshell.rst
@@ -0,0 +1,13 @@
+.. index::
+ single: pshell; --help
+
+.. _pshell_script:
+
+``pshell``
+----------
+
+.. program-output:: pshell --help
+ :prompt:
+ :shell:
+
+.. seealso:: :ref:`interactive_shell`
diff --git a/docs/pscripts/ptweens.rst b/docs/pscripts/ptweens.rst
new file mode 100644
index 000000000..02e23e49a
--- /dev/null
+++ b/docs/pscripts/ptweens.rst
@@ -0,0 +1,13 @@
+.. index::
+ single: ptweens; --help
+
+.. _ptweens_script:
+
+``ptweens``
+-----------
+
+.. program-output:: ptweens --help
+ :prompt:
+ :shell:
+
+.. seealso:: :ref:`displaying_tweens`
diff --git a/docs/pscripts/pviews.rst b/docs/pscripts/pviews.rst
new file mode 100644
index 000000000..b4de5c054
--- /dev/null
+++ b/docs/pscripts/pviews.rst
@@ -0,0 +1,13 @@
+.. index::
+ single: pviews; --help
+
+.. _pviews_script:
+
+``pviews``
+----------
+
+.. program-output:: pviews --help
+ :prompt:
+ :shell:
+
+.. seealso:: :ref:`displaying_matching_views`
diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst
index be5be2e36..fb957be1c 100644
--- a/docs/quick_tour.rst
+++ b/docs/quick_tour.rst
@@ -1,3 +1,4 @@
+
.. _quick_tour:
=====================
@@ -5,73 +6,78 @@ Quick Tour of Pyramid
=====================
Pyramid lets you start small and finish big. This *Quick Tour* of Pyramid is
-for those who want to evaluate Pyramid, whether you are new to Python
-web frameworks, or a pro in a hurry. For more detailed treatment of
-each topic, give the :ref:`quick_tutorial` a try.
+for those who want to evaluate Pyramid, whether you are new to Python web
+frameworks, or a pro in a hurry. For more detailed treatment of each topic,
+give the :ref:`quick_tutorial` a try.
+
Installation
============
-Once you have a standard Python environment setup, getting started with
-Pyramid is a breeze. Unfortunately "standard" is not so simple in Python.
-For this Quick Tour, it means: `Python <https://www.python.org/downloads/>`_,
-a `virtual environment <http://docs.python.org/dev/library/venv.html>`_ (or
-`virtualenv for Python 2.7 <https://pypi.python.org/pypi/virtualenv>`_), and
-`setuptools <https://pypi.python.org/pypi/setuptools/>`_.
+Once you have a standard Python environment setup, getting started with Pyramid
+is a breeze. Unfortunately "standard" is not so simple in Python. For this
+Quick Tour, it means `Python <https://www.python.org/downloads/>`_, `venv
+<https://packaging.python.org/en/latest/projects/#venv>`_ (or `virtualenv for
+Python 2.7 <https://packaging.python.org/en/latest/projects/#virtualenv>`_),
+`pip <https://packaging.python.org/en/latest/projects/#pip>`_, and `setuptools
+<https://packaging.python.org/en/latest/projects/#easy-install>`_.
+
+To save a little bit of typing and to be certain that we use the modules,
+scripts, and packages installed in our virtual environment, we'll set an
+environment variable, too.
-As an example, for Python 3.3+ on Linux:
+As an example, for Python 3.5+ on Linux:
.. parsed-literal::
- $ pyvenv env33
- $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | env33/bin/python
- $ env33/bin/easy_install "pyramid==\ |release|\ "
+ # set an environment variable to where you want your virtual environment
+ $ export VENV=~/env
+ # create the virtual environment
+ $ python3 -m venv $VENV
+ # install pyramid
+ $ $VENV/bin/pip install pyramid
+ # or for a specific released version
+ $ $VENV/bin/pip install "pyramid==\ |release|\ "
For Windows:
.. parsed-literal::
- # Use your browser to download:
- # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
- c:\\> c:\\Python33\\python -m venv env33
- c:\\> env33\\Scripts\\python ez_setup.py
- c:\\> env33\\Scripts\\easy_install "pyramid==\ |release|\ "
+ # set an environment variable to where you want your virtual environment
+ c:\\> set VENV=c:\\env
+ # create the virtual environment
+ c:\\> %VENV%\\Scripts\\python -m venv %VENV%
+ # install pyramid
+ c:\\> %VENV%\\Scripts\\pip install pyramid
+ # or for a specific released version
+ c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
-Of course Pyramid runs fine on Python 2.6+, as do the examples in this
-*Quick Tour*. We're just showing Python 3 a little love (Pyramid had
-production support for Python 3 in October 2011).
-
-.. note::
-
- Why ``easy_install`` and not ``pip``? Some distributions on which Pyramid
- depends upon have optional C extensions for performance. ``pip`` cannot
- install some binary Python distributions. With ``easy_install``, Windows
- users are able to obtain binary Python distributions, so they get the
- benefit of the C extensions without needing a C compiler. Also, there can
- be issues when ``pip`` and ``easy_install`` are used side-by-side in the
- same environment, so we chose to recommend ``easy_install`` for the sake of
- reducing the complexity of these instructions.
+Of course Pyramid runs fine on Python 2.6+, as do the examples in this *Quick
+Tour*. We're showing Python 3 for simplicity. (Pyramid had production support
+for Python 3 in October 2011.) Also for simplicity, the remaining examples will
+show only UNIX commands.
.. seealso:: See also:
:ref:`Quick Tutorial section on Requirements <qtut_requirements>`,
- :ref:`installing_unix`,
- :ref:`Before You Install <installing_chapter>`, and
- :ref:`Installing Pyramid on a Windows System <installing_windows>`
+ :ref:`installing_unix`, :ref:`Before You Install <installing_chapter>`, and
+ :ref:`Installing Pyramid on a Windows System <installing_windows>`.
+
Hello World
===========
-Microframeworks have shown that learning starts best from a very small
-first step. Here's a tiny application in Pyramid:
+Microframeworks have shown that learning starts best from a very small first
+step. Here's a tiny application in Pyramid:
.. literalinclude:: quick_tour/hello_world/app.py
:linenos:
+ :language: python
This simple example is easy to run. Save this as ``app.py`` and run it:
.. code-block:: bash
- $ python ./app.py
+ $ $VENV/bin/python ./app.py
Next open http://localhost:6543/ in a browser, and you will see the ``Hello
World!`` message.
@@ -79,65 +85,66 @@ World!`` message.
New to Python web programming? If so, some lines in the module merit
explanation:
-#. *Line 10*. The ``if __name__ == '__main__':`` is Python's way of
- saying "Start here when running from the command line".
+#. *Line 10*. ``if __name__ == '__main__':`` is Python's way of saying "Start
+ here when running from the command line".
-#. *Lines 11-13*. Use Pyramid's :term:`configurator` to connect
- :term:`view` code to a particular URL :term:`route`.
+#. *Lines 11-13*. Use Pyramid's :term:`configurator` to connect :term:`view`
+ code to a particular URL :term:`route`.
-#. *Lines 6-7*. Implement the view code that generates the
- :term:`response`.
+#. *Lines 6-7*. Implement the view code that generates the :term:`response`.
#. *Lines 14-16*. Publish a :term:`WSGI` app using an HTTP server.
-As shown in this example, the :term:`configurator` plays a central role
-in Pyramid development. Building an application from loosely-coupled
-parts via :doc:`../narr/configuration` is a central idea in Pyramid,
-one that we will revisit regurlarly in this *Quick Tour*.
+As shown in this example, the :term:`configurator` plays a central role in
+Pyramid development. Building an application from loosely-coupled parts via
+:doc:`../narr/configuration` is a central idea in Pyramid, one that we will
+revisit regurlarly in this *Quick Tour*.
.. seealso:: See also:
:ref:`Quick Tutorial Hello World <qtut_hello_world>`,
- :ref:`firstapp_chapter`, and
- :ref:`Single File Tasks tutorial <tutorials:single-file-tutorial>`
+ :ref:`firstapp_chapter`, and :ref:`Todo List Application in One File
+ <cookbook:single-file-tutorial>`.
+
Handling web requests and responses
===================================
-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.
+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.
-Pyramid has always fit nicely into the existing world of Python web
-development (virtual environments, packaging, scaffolding, one of the first to
-embrace Python 3, etc.). 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>`.
+Pyramid has always fit nicely into the existing world of Python web development
+(virtual environments, packaging, scaffolding, one of the first to embrace
+Python 3, etc.). 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>`.
Let's see some features of requests and responses in action:
.. literalinclude:: quick_tour/requests/app.py
+ :language: python
:pyobject: hello_world
-In this Pyramid view, we get the URL being visited from ``request.url``. Also,
+In this Pyramid view, we get the URL being visited from ``request.url``. Also
if you visited http://localhost:6543/?name=alice in a browser, the name is
-included in the body of the response::
+included in the body of the response:
+
+.. code-block:: text
URL http://localhost:6543/?name=alice with name: alice
-Finally, we set the response's content type and return the Response.
+Finally we set the response's content type, and return the Response.
.. seealso:: See also:
- :ref:`Quick Tutorial Request and Response <qtut_request_response>`
- and
- :ref:`webob_chapter`
+ :ref:`Quick Tutorial Request and Response <qtut_request_response>` and
+ :ref:`webob_chapter`.
+
Views
=====
-For the examples above, the ``hello_world`` function is a "view". In
-Pyramid, views are the primary way to accept web requests and return
-responses.
+For the examples above, 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:
@@ -149,169 +156,177 @@ So far our examples place everything in one file:
- the WSGI application launcher
-Let's move the views out to their own ``views.py`` module and change
-the ``app.py`` to scan that module, looking for decorators that set up
-the views.
+Let's move the views out to their own ``views.py`` module and change the
+``app.py`` to scan that module, looking for decorators that set up the views.
-First, our revised ``app.py``:
+First our revised ``app.py``:
.. literalinclude:: quick_tour/views/app.py
+ :language: python
:linenos:
-We added some more routes, but we also removed the view code.
-Our views and their registrations (via decorators) are now in a module
-``views.py``, which is scanned via ``config.scan('views')``.
+We added some more routes, but we also removed the view code. Our views and
+their registrations (via decorators) are now in a module ``views.py``, which is
+scanned via ``config.scan('views')``.
-We now have a ``views.py`` module that is focused on handling requests
-and responses:
+We now have a ``views.py`` module that is focused on handling requests and
+responses:
.. literalinclude:: quick_tour/views/views.py
+ :language: python
:linenos:
We have four 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
-to the ``redirect_view``, which issues a redirect to the final
-view.
-
-Earlier we saw ``config.add_view`` as one way to configure a view. This
-section introduces ``@view_config``. Pyramid's configuration supports
-:term:`imperative configuration`, such as the ``config.add_view`` in
-the previous example. You can also use :term:`declarative
-configuration`, in which a Python :term:`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.
+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 to the
+``redirect_view``, which issues a redirect to the final view.
+
+Earlier we saw ``config.add_view`` as one way to configure a view. This section
+introduces ``@view_config``. Pyramid's configuration supports :term:`imperative
+configuration`, such as the ``config.add_view`` in the previous example. You
+can also use :term:`declarative configuration` in which a Python
+:term:`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.
.. seealso:: See also:
- :ref:`Quick Tutorial Views <qtut_views>`,
- :doc:`../narr/views`,
- :doc:`../narr/viewconfig`, and
- :ref:`debugging_view_configuration`
+ :ref:`Quick Tutorial Views <qtut_views>`, :doc:`../narr/views`,
+ :doc:`../narr/viewconfig`, and :ref:`debugging_view_configuration`.
+
Routing
=======
-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.
+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
+with routing.
Above 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
+- 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
+- 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.
+ 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.
-What if we want part of the URL to be available as data in my view? This
-route declaration:
+What if we want part of the URL to be available as data in my view? We can use
+this route declaration, for example:
.. literalinclude:: quick_tour/routing/app.py
- :start-after: Start Route 1
- :end-before: End Route 1
+ :language: python
+ :linenos:
+ :lines: 6
+ :lineno-start: 6
-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:
+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:
.. literalinclude:: quick_tour/routing/views.py
- :start-after: Start Route 1
- :end-before: End Route 1
+ :language: python
+ :linenos:
+ :lines: 5-8
+ :lineno-start: 5
+ :emphasize-lines: 3
-``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 in your view.
+``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 in your view.
.. seealso:: See also:
- :ref:`Quick Tutorial Routing <qtut_routing>`,
- :doc:`../narr/urldispatch`,
- :ref:`debug_routematch_section`, and
- :doc:`../narr/router`
+ :ref:`Quick Tutorial Routing <qtut_routing>`, :doc:`../narr/urldispatch`,
+ :ref:`debug_routematch_section`, and :doc:`../narr/router`.
+
Templating
==========
-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.
+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 you 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, the Pylons Project officially supports bindings for
-Chameleon, Jinja2, and Mako, so in this step, let's use Chameleon.
+Pyramid doesn't mandate a particular database system, form library, and so on.
+It encourages replaceability. This applies equally to templating, which is
+fortunate: developers have strong views about template languages. That said,
+the Pylons Project officially supports bindings for Chameleon, Jinja2, and
+Mako. In this step let's use Chameleon.
Let's add ``pyramid_chameleon``, a Pyramid :term:`add-on` which enables
-Chameleon as a :term:`renderer` in our Pyramid applications:
+Chameleon as a :term:`renderer` in our Pyramid application:
.. code-block:: bash
- $ easy_install pyramid_chameleon
+ $ $VENV/bin/pip install pyramid_chameleon
-With the package installed, we can include the template bindings into
-our configuration:
+With the package installed, we can include the template bindings into our
+configuration in ``app.py``:
-.. code-block:: python
+.. literalinclude:: quick_tour/templating/app.py
+ :language: python
+ :linenos:
+ :lines: 6-8
+ :lineno-start: 6
+ :emphasize-lines: 2
- config.include('pyramid_chameleon')
-
-Now lets change our views.py file:
+Now lets change our ``views.py`` file:
.. literalinclude:: quick_tour/templating/views.py
- :start-after: Start View 1
- :end-before: End View 1
+ :language: python
+ :linenos:
+ :emphasize-lines: 4,6
-Ahh, that looks better. We have a view that is focused on Python code.
-Our ``@view_config`` decorator specifies a :term:`renderer` that points
-to our template file. Our view then simply returns data which is then
-supplied to our template:
+Ahh, that looks better. We have a view that is focused on Python code. Our
+``@view_config`` decorator specifies a :term:`renderer` that points to our
+template file. Our view then simply returns data which is then supplied to our
+template ``hello_world.pt``:
.. literalinclude:: quick_tour/templating/hello_world.pt
:language: html
-Since our view returned ``dict(name=request.matchdict['name'])``,
-we can use ``name`` as a variable in our template via
-``${name}``.
+Since our view returned ``dict(name=request.matchdict['name'])``, we can use
+``name`` as a variable in our template via ``${name}``.
.. seealso:: See also:
:ref:`Quick Tutorial Templating <qtut_templating>`,
- :doc:`../narr/templates`,
- :ref:`debugging_templates`, and
- :ref:`available_template_system_bindings`
+ :doc:`../narr/templates`, :ref:`debugging_templates`, and
+ :ref:`available_template_system_bindings`.
-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,
-modeled 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:
+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, modeled 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:
.. code-block:: bash
- $ easy_install pyramid_jinja2
+ $ $VENV/bin/pip install pyramid_jinja2
-With the package installed, we can include the template bindings into
-our configuration:
+With the package installed, we can include the template bindings into our
+configuration:
-.. code-block:: python
-
- config.include('pyramid_jinja2')
+.. literalinclude:: quick_tour/jinja2/app.py
+ :language: python
+ :linenos:
+ :lines: 6-8
+ :lineno-start: 6
+ :emphasize-lines: 2
The only change in our view is to point the renderer at the ``.jinja2`` file:
.. literalinclude:: quick_tour/jinja2/views.py
- :start-after: Start View 1
- :end-before: End View 1
+ :language: python
+ :linenos:
+ :lines: 4-6
+ :lineno-start: 4
+ :emphasize-lines: 1
Our Jinja2 template is very similar to our previous template:
@@ -319,93 +334,112 @@ Our Jinja2 template is very similar to our previous template:
:language: html
Pyramid's templating add-ons register a new kind of renderer into your
-application. The renderer registration maps to different kinds of
-filename extensions. In this case, changing the extension from ``.pt``
-to ``.jinja2`` passed the view response through the ``pyramid_jinja2``
-renderer.
+application. The renderer registration maps to different kinds of filename
+extensions. In this case, changing the extension from ``.pt`` to ``.jinja2``
+passed the view response through the ``pyramid_jinja2`` renderer.
.. seealso:: See also:
- :ref:`Quick Tutorial Jinja2 <qtut_jinja2>`,
- `Jinja2 homepage <http://jinja.pocoo.org/>`_, and
- :ref:`pyramid_jinja2 Overview <jinja2:overview>`
+ :ref:`Quick Tutorial Jinja2 <qtut_jinja2>`, `Jinja2 homepage
+ <http://jinja.pocoo.org/>`_, and :ref:`pyramid_jinja2 Overview
+ <jinja2:overview>`.
+
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. First another call to the
-:term:`configurator`:
+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 from which Pyramid will
+serve some static assets. First let's make another call to the
+:term:`configurator` in ``app.py``:
.. literalinclude:: quick_tour/static_assets/app.py
- :start-after: Start Static 1
- :end-before: End Static 1
+ :language: python
+ :linenos:
+ :lines: 6-8
+ :lineno-start: 6
+ :emphasize-lines: 2
This tells our WSGI application to map requests under
-http://localhost:6543/static/ to files and directories inside a
-``static`` directory alongside our Python module.
+http://localhost:6543/static/ to files and directories inside a ``static``
+directory alongside our Python module.
Next make a directory named ``static``, and place ``app.css`` inside:
.. literalinclude:: quick_tour/static_assets/static/app.css
:language: css
-All we need to do now is point to it in the ``<head>`` of our Jinja2
-template:
+All we need to do now is point to it in the ``<head>`` of our Jinja2 template,
+``hello_world.jinja2``:
-.. literalinclude:: quick_tour/static_assets/hello_world.pt
- :language: html
- :start-after: Start Link 1
- :end-before: End Link 1
+.. literalinclude:: quick_tour/static_assets/hello_world_static.jinja2
+ :language: jinja
+ :linenos:
+ :lines: 4-6
+ :lineno-start: 4
+ :emphasize-lines: 2
-This link presumes that our CSS is at a URL starting with ``/static/``.
-What if the site is later moved under ``/somesite/static/``? Or perhaps
-a web developer changes the arrangement on disk? Pyramid provides a helper
-to allow flexibility on URL generation:
+This link presumes that our CSS is at a URL starting with ``/static/``. What if
+the site is later moved under ``/somesite/static/``? Or perhaps a web developer
+changes the arrangement on disk? Pyramid provides a helper to allow flexibility
+on URL generation:
-.. literalinclude:: quick_tour/static_assets/hello_world.pt
- :language: html
- :start-after: Start Link 2
- :end-before: End Link 2
+.. literalinclude:: quick_tour/static_assets/hello_world.jinja2
+ :language: jinja
+ :linenos:
+ :lines: 4-6
+ :lineno-start: 4
+ :emphasize-lines: 2
-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.
+By using ``request.static_url`` to generate the full URL to the static assets,
+you ensure that you stay in sync with the configuration and gain refactoring
+flexibility later.
.. seealso:: See also:
:ref:`Quick Tutorial Static Assets <qtut_static_assets>`,
- :doc:`../narr/assets`,
- :ref:`preventing_http_caching`, and
- :ref:`influencing_http_caching`
+ :doc:`../narr/assets`, :ref:`preventing_http_caching`, and
+ :ref:`influencing_http_caching`.
+
Returning JSON
==============
-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:
+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:
.. literalinclude:: quick_tour/json/views.py
- :start-after: Start View 1
- :end-before: End View 2
+ :language: python
+ :linenos:
+ :lines: 9-
+ :lineno-start: 9
+
+This wires up a view that returns some data through the JSON :term:`renderer`,
+which calls Python's JSON support to serialize the data into JSON, and sets the
+appropriate HTTP headers.
-This wires up a view that returns some data through the JSON
-:term:`renderer`, which calls Python's JSON support to serialize the data
-into JSON and set the appropriate HTTP headers.
+We also need to add a route to ``app.py`` so that our app will know how to
+respond to a request for ``hello.json``.
+
+.. literalinclude:: quick_tour/json/app.py
+ :language: python
+ :linenos:
+ :lines: 6-8
+ :lineno-start: 6
+ :emphasize-lines: 2
.. seealso:: See also:
- :ref:`Quick Tutorial JSON <qtut_json>`,
- :ref:`views_which_use_a_renderer`,
- :ref:`json_renderer`, and
- :ref:`adding_and_overriding_renderers`
+ :ref:`Quick Tutorial JSON <qtut_json>`, :ref:`views_which_use_a_renderer`,
+ :ref:`json_renderer`, and :ref:`adding_and_overriding_renderers`.
+
View classes
============
-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 <class_as_view>` makes sense.
+So far our views have been simple, free-standing functions. Many times your
+views are related. They may have different ways to look at or work on the same
+data, or they may be a REST API that handles multiple operations. Grouping
+these together as a :ref:`view class <class_as_view>` makes sense and achieves
+the following goals.
- Group views
@@ -413,90 +447,90 @@ together as a :ref:`view class <class_as_view>` makes sense.
- Share some state and helpers
-The following shows a "Hello World" example with three operations: view
-a form, save a change, or press the delete button:
+The following shows a "Hello World" example with three operations: view a form,
+save a change, or press the delete button in our ``views.py``:
.. literalinclude:: quick_tour/view_classes/views.py
- :start-after: Start View 1
- :end-before: End View 1
+ :language: python
+ :linenos:
+ :lines: 7-
+ :lineno-start: 7
-As you can see, the three views are logically grouped together.
-Specifically:
+As you can see, the three views are logically grouped together. Specifically:
-- The first view is returned when you go to ``/howdy/amy``. This URL is
- mapped to the ``hello`` route that we centrally set using the optional
+- The first view is returned when you go to ``/howdy/amy``. This URL is mapped
+ to the ``hello`` route that we centrally set using the optional
``@view_defaults``.
- The second view is returned when the form data contains a field with
- ``form.edit``, such as clicking on
- ``<input type="submit" name="form.edit" value="Save">``. This rule
- is specified in the ``@view_config`` for that view.
+ ``form.edit``, such as clicking on ``<input type="submit" name="form.edit"
+ value="Save">``. This rule is specified in the ``@view_config`` for that
+ view.
-- The third view is returned when clicking on a button such
- as ``<input type="submit" name="form.delete" value="Delete">``.
+- The third view is returned when clicking on a button such as ``<input
+ type="submit" name="form.delete" value="Delete">``.
-Only one route is needed, stated in one place atop the view class. Also,
-the assignment of ``name`` is done in the ``__init__`` function. Our
-templates can then use ``{{ view.name }}``.
+Only one route is needed, stated in one place atop the view class. Also, the
+assignment of ``name`` is done in the ``__init__`` function. Our templates can
+then use ``{{ view.name }}``.
-Pyramid view classes, combined with built-in and custom predicates,
-have much more to offer:
+Pyramid view classes, combined with built-in and custom predicates, have much
+more to offer:
- All the same view configuration parameters as function views
-- One route leading to multiple views, based on information in the
- request or data such as ``request_param``, ``request_method``,
- ``accept``, ``header``, ``xhr``, ``containment``, and
- ``custom_predicates``
+- One route leading to multiple views, based on information in the request or
+ data such as ``request_param``, ``request_method``, ``accept``, ``header``,
+ ``xhr``, ``containment``, and ``custom_predicates``
.. seealso:: See also:
- :ref:`Quick Tutorial View Classes <qtut_view_classes>`,
- :ref:`Quick Tutorial More View Classes <qtut_more_view_classes>`, and
- :ref:`class_as_view`
+ :ref:`Quick Tutorial View Classes <qtut_view_classes>`, :ref:`Quick
+ Tutorial More View Classes <qtut_more_view_classes>`, and
+ :ref:`class_as_view`.
+
Quick project startup with scaffolds
====================================
-So far we have done all of our *Quick Tour* as a single Python file.
-No Python packages, no structure. Most Pyramid projects, though,
-aren't developed this way.
+So far we have done all of our *Quick Tour* as a single Python file. No Python
+packages, no structure. Most Pyramid projects, though, aren't developed this
+way.
-To ease the process of getting started, Pyramid provides *scaffolds*
-that generate sample projects from templates in Pyramid and Pyramid
-add-ons. Pyramid's ``pcreate`` command can list the available scaffolds:
+To ease the process of getting started, Pyramid provides *scaffolds* that
+generate sample projects from templates in Pyramid and Pyramid add-ons.
+Pyramid's ``pcreate`` command can list the available scaffolds:
.. code-block:: bash
$ pcreate --list
Available scaffolds:
alchemy: Pyramid SQLAlchemy project using url dispatch
- pyramid_jinja2_starter: pyramid jinja2 starter project
+ pyramid_jinja2_starter: Pyramid Jinja2 starter project
starter: Pyramid starter project
zodb: Pyramid ZODB project using traversal
-The ``pyramid_jinja2`` add-on gave us a scaffold that we can use. From
-the parent directory of where we want our Python package to be generated,
-let's use that scaffold to make our project:
+The ``pyramid_jinja2`` add-on gave us a scaffold that we can use. From the
+parent directory of where we want our Python package to be generated, let's use
+that scaffold to make our project:
.. code-block:: bash
$ pcreate --scaffold pyramid_jinja2_starter hello_world
-We next use the normal Python command to set up our package for
-development:
+We next use the normal Python command to set up our package for development:
.. code-block:: bash
$ cd hello_world
- $ python ./setup.py develop
+ $ $VENV/bin/pip install -e .
-We are moving in the direction of a full-featured Pyramid project,
-with a proper setup for Python standards (packaging) and Pyramid
-configuration. This includes a new way of running your application:
+We are moving in the direction of a full-featured Pyramid project, with a
+proper setup for Python standards (packaging) and Pyramid configuration. This
+includes a new way of running your application:
.. code-block:: bash
- $ pserve development.ini
+ $ $VENV/bin/pserve development.ini
Let's look at ``pserve`` and configuration in more depth.
@@ -508,28 +542,27 @@ Let's look at ``pserve`` and configuration in more depth.
Application running with ``pserve``
===================================
-Prior to scaffolds, our project mixed a number of operational details
-into our code. Why should my main code care which HTTP server I want and
-what port number to run on?
+Prior to scaffolds, our project mixed a number of operational details into our
+code. Why should my main code care which HTTP server I want and what port
+number to run 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's very small, getting most
-of its brains via import.
+``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's very small, getting most of its brains via import.
-You can run ``pserve`` with ``--help`` to see some of its options.
-Doing so reveals that you can ask ``pserve`` to watch your development
-files and reload the server when they change:
+You can run ``pserve`` with ``--help`` to see some of its options. Doing so
+reveals that you can ask ``pserve`` to watch your development files and reload
+the server when they change:
.. code-block:: bash
- $ pserve development.ini --reload
+ $ $VENV/bin/pserve development.ini --reload
-The ``pserve`` command has a number of other options and operations.
-Most of the work, though, comes from your project's wiring, as
-expressed in the configuration file you supply to ``pserve``. Let's
-take a look at this configuration file.
+The ``pserve`` command has a number of other options and operations. Most of
+the work, though, comes from your project's wiring, as expressed in the
+configuration file you supply to ``pserve``. Let's take a look at this
+configuration file.
.. seealso:: See also:
:ref:`what_is_this_pserve_thing`
@@ -537,21 +570,18 @@ take a look at this configuration file.
Configuration with ``.ini`` files
=================================
-Earlier in *Quick Tour* we first met Pyramid's configuration system.
-At that point we did all configuration in Python code. For example,
-the port number chosen for our HTTP server was right there in Python
-code. Our scaffold has moved this decision and more into the
-``development.ini`` file:
+Earlier in *Quick Tour* we first met Pyramid's configuration system. At that
+point we did all configuration in Python code. For example, the port number
+chosen for our HTTP server was right there in Python code. Our scaffold has
+moved this decision and more into the ``development.ini`` file:
.. literalinclude:: quick_tour/package/development.ini
:language: ini
-Let's take a quick high-level look. First the ``.ini`` file is divided
-into sections:
-
-- ``[app:hello_world]`` configures our WSGI app
+Let's take a quick high-level look. First the ``.ini`` file is divided into
+sections:
-- ``[pipeline:main]`` sets up our WSGI "pipeline"
+- ``[app:main]`` configures our WSGI app
- ``[server:main]`` holds our WSGI server settings
@@ -559,23 +589,23 @@ into sections:
We have a few decisions made for us in this configuration:
-#. *Choice of web server:* ``use = egg:pyramid#wsgiref`` tells ``pserve`` to
- use the ``wsgiref`` server that is wrapped in the Pyramid package.
+#. *Choice of web server:* ``use = egg:hello_world`` tells ``pserve`` to
+ use the ``waitress`` server.
-#. *Port number:* ``port = 6543`` tells ``wsgiref`` to listen on port 6543.
+#. *Port number:* ``port = 6543`` tells ``waitress`` to listen on port 6543.
#. *WSGI app:* What package has our WSGI application in it?
- ``use = egg:hello_world`` in the app section tells the
- configuration what application to load.
+ ``use = egg:hello_world`` in the app section tells the configuration what
+ application to load.
#. *Easier development by automatic template reloading:* In development mode,
you shouldn't have to restart the server when editing a Jinja2 template.
- ``reload_templates = true`` sets this policy, which might be different in
- production.
+ ``pyramid.reload_templates = true`` sets this policy, which might be
+ different in production.
-Additionally the ``development.ini`` generated by this scaffold wired
-up Python's standard logging. We'll now see in the console, for example,
-a log on every request that comes in, as well as traceback information.
+Additionally the ``development.ini`` generated by this scaffold wired up
+Python's standard logging. We'll now see in the console, for example, a log on
+every request that comes in, as well as traceback information.
.. seealso:: See also:
:ref:`Quick Tutorial Application Configuration <qtut_ini>`,
@@ -587,267 +617,316 @@ Easier development with ``debugtoolbar``
========================================
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.
+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.
+``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.
-First change your ``setup.py`` to say:
+The scaffold ``pyramid_jinja2_starter`` is already configured to include the
+add-on ``pyramid_debugtoolbar`` in its ``setup.py``:
.. literalinclude:: quick_tour/package/setup.py
- :start-after: Start Requires
- :end-before: End Requires
+ :language: python
+ :linenos:
+ :lineno-start: 11
+ :lines: 11-16
-...and rerun your setup:
+It was installed when you previously ran:
.. code-block:: bash
- $ python ./setup.py develop
+ $ $VENV/bin/pip install -e .
-The Python package ``pyramid_debugtoolbar`` is now installed into our
-environment. The package is a Pyramid add-on, which means we need to include
-its configuration into our web application. We could do this with imperative
-configuration, as we did above for the ``pyramid_jinja2`` add-on:
+The ``pyramid_debugtoolbar`` package is a Pyramid add-on, which means we need
+to include its configuration into our web application. The ``pyramid_jinja2``
+add-on already took care of this for us in its ``__init__.py``:
.. literalinclude:: quick_tour/package/hello_world/__init__.py
- :start-after: Start Include
- :end-before: End Include
+ :language: python
+ :linenos:
+ :lineno-start: 16
+ :lines: 19
-Now that we have a configuration file, we can use the
-``pyramid.includes`` facility and place this in our
-``development.ini`` instead:
+And it uses the ``pyramid.includes`` facility in our ``development.ini``:
.. literalinclude:: quick_tour/package/development.ini
:language: ini
- :start-after: Start Includes
- :end-before: End Includes
-
-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, there's no need to change code: you can
-remove it from ``pyramid.includes`` in the relevant ``.ini``
-configuration file.
+ :linenos:
+ :lineno-start: 15
+ :lines: 15-16
+
+You'll now see a Pyramid logo on the right side of your browser window, which
+when clicked opens a new window that provides 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,
+there's no need to change code: you can remove it from ``pyramid.includes`` in
+the relevant ``.ini`` configuration file.
.. seealso:: See also:
- :ref:`Quick Tutorial
- pyramid_debugtoolbar <qtut_debugtoolbar>` and
+ :ref:`Quick Tutorial pyramid_debugtoolbar <qtut_debugtoolbar>` and
:ref:`pyramid_debugtoolbar <toolbar:overview>`
-Unit tests and ``nose``
-=======================
+Unit tests and ``py.test``
+==========================
+
+Yikes! We got this far and we haven't yet discussed tests. This is particularly
+egregious, as Pyramid has had a deep commitment to full test coverage since
+before its release.
-Yikes! We got this far and we haven't yet discussed tests. This is
-particularly egregious, as Pyramid has had a deep commitment to full test
-coverage since before its release.
+Our ``pyramid_jinja2_starter`` scaffold generated a ``tests.py`` module with
+one unit test in it. To run it, let's install the handy ``pytest`` test runner
+by editing ``setup.py``. While we're at it, we'll throw in the ``pytest-cov``
+tool which yells at us for code that isn't tested. Insert and edit the
+following lines as shown:
-Our ``pyramid_jinja2_starter`` scaffold generated a ``tests.py`` module
-with one unit test in it. To run it, let's install the handy ``nose``
-test runner by editing ``setup.py``. While we're at it, we'll throw in
-the ``coverage`` tool which yells at us for code that isn't tested:
+.. code-block:: python
+ :linenos:
+ :lineno-start: 11
+ :emphasize-lines: 8-12
+
+ requires = [
+ 'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'waitress',
+ ]
+
+ tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
.. code-block:: python
+ :linenos:
+ :lineno-start: 34
+ :emphasize-lines: 2-4
- setup(name='hello_world',
- # Some lines removed...
- extras_require={
- 'testing': ['nose', 'coverage'],
- }
- )
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
-We changed ``setup.py`` which means we need to rerun
-``python ./setup.py develop``. We can now run all our tests:
+We changed ``setup.py`` which means we need to rerun ``$VENV/bin/pip install -e
+".[testing]"``. We can now run all our tests:
.. code-block:: bash
- $ nosetests hello_world/tests.py
- .
- Name Stmts Miss Cover Missing
- ---------------------------------------------------
- hello_world 12 8 33% 11-23
- hello_world.models 5 1 80% 8
- hello_world.tests 14 0 100%
- hello_world.views 4 0 100%
- ---------------------------------------------------
- TOTAL 35 9 74%
- ----------------------------------------------------------------------
- Ran 1 test in 0.931s
+ $ $VENV/bin/py.test --cov --cov-report=term-missing
- OK
+This yields the following output.
-Our unit test passed. What did our test look like?
+.. code-block:: text
+
+ =========================== test session starts ===========================
+ platform darwin -- Python 3.5.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
+ rootdir: /Users/stevepiercy/projects/hack-on-pyramid/hello_world, inifile:
+ plugins: cov-2.2.1
+ collected 1 items
+
+ hello_world/tests.py .
+ ------------- coverage: platform darwin, python 3.5.0-final-0 -------------
+ Name Stmts Miss Cover Missing
+ --------------------------------------------------------
+ hello_world/__init__.py 11 8 27% 11-23
+ hello_world/resources.py 5 1 80% 8
+ hello_world/tests.py 14 0 100%
+ hello_world/views.py 4 0 100%
+ --------------------------------------------------------
+ TOTAL 34 9 74%
+
+ ========================= 1 passed in 0.22 seconds =========================
+
+Our unit test passed, although its coverage is incomplete. What did our test
+look like?
.. literalinclude:: quick_tour/package/hello_world/tests.py
+ :language: python
+ :linenos:
-Pyramid supplies helpers for test writing, 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.
+Pyramid supplies helpers for test writing, 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.
.. seealso:: See also:
- :ref:`Quick Tutorial Unit Testing <qtut_unit_testing>`,
- :ref:`Quick Tutorial Functional Testing <qtut_functional_testing>`,
- and
+ :ref:`Quick Tutorial Unit Testing <qtut_unit_testing>`, :ref:`Quick
+ Tutorial Functional Testing <qtut_functional_testing>`, and
:ref:`testing_chapter`
Logging
=======
-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 situations when other people use the site. We
-need *logging*.
+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 situations 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`` has 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).
+Fortunately Pyramid uses the normal Python approach to logging. The scaffold
+generated in your ``development.ini`` has 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).
-Maybe you would like to log messages in your code? In your Python
-module, import and set up the logging:
+Maybe you would like to log messages in your code? In your Python module,
+import and set up the logging:
.. literalinclude:: quick_tour/package/hello_world/views.py
- :start-after: Start Logging 1
- :end-before: End Logging 1
+ :language: python
+ :linenos:
+ :lineno-start: 3
+ :lines: 3-4
You can now, in your code, log messages:
.. literalinclude:: quick_tour/package/hello_world/views.py
- :start-after: Start Logging 2
- :end-before: End Logging 2
+ :language: python
+ :linenos:
+ :lineno-start: 9
+ :lines: 9-10
+ :emphasize-lines: 2
-This will log ``Some Message`` at a ``debug`` log level
-to the application-configured logger in your ``development.ini``. What
-controls that? These sections in the configuration file:
+This will log ``Some Message`` at a ``debug`` log level to the
+application-configured logger in your ``development.ini``. What controls that?
+These emphasized sections in the configuration file:
.. literalinclude:: quick_tour/package/development.ini
:language: ini
- :start-after: Start Sphinx Include
- :end-before: End Sphinx Include
+ :linenos:
+ :lineno-start: 36
+ :lines: 36-52
+ :emphasize-lines: 1-2,14-17
+
+Our application, a package named ``hello_world``, is set up 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:
-Our application, a package named ``hello_world``, is set up 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::
+.. code-block:: text
- 2013-08-09 10:42:42,968 DEBUG [hello_world.views][MainThread] Some Message
+ 2016-01-18 13:55:55,040 DEBUG [hello_world.views:10][waitress] Some Message
.. seealso:: See also:
- :ref:`Quick Tutorial Logging <qtut_logging>` and
- :ref:`logging_chapter`
+ :ref:`Quick Tutorial Logging <qtut_logging>` and :ref:`logging_chapter`.
Sessions
========
-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`.
+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. Third party packages such as
-``pyramid_redis_sessions`` provide richer session support. Or you can create
-your own custom sessioning engine. Let's take a look at the
-:doc:`built-in sessioning support <../narr/sessions>`. In our
-``__init__.py`` we first import the kind of sessioning we want:
+Pyramid has basic built-in support for sessions. Third party packages such as
+``pyramid_redis_sessions`` provide richer session support. Or you can create
+your own custom sessioning engine. Let's take a look at the :doc:`built-in
+sessioning support <../narr/sessions>`. In our ``__init__.py`` we first import
+the kind of sessioning we want:
.. literalinclude:: quick_tour/package/hello_world/__init__.py
- :start-after: Start Sphinx Include 1
- :end-before: End Sphinx Include 1
+ :language: python
+ :linenos:
+ :lineno-start: 2
+ :lines: 2-3
+ :emphasize-lines: 2
.. warning::
- As noted in the session docs, this example implementation is
- not intended for use in settings with security implications.
+ As noted in the session docs, this example implementation is not intended
+ for use in settings with security implications.
Now make a "factory" and pass it to the :term:`configurator`'s
``session_factory`` argument:
.. literalinclude:: quick_tour/package/hello_world/__init__.py
- :start-after: Start Sphinx Include 2
- :end-before: End Sphinx Include 2
+ :language: python
+ :linenos:
+ :lineno-start: 13
+ :lines: 13-17
+ :emphasize-lines: 3-5
-Pyramid's :term:`request` object now has a ``session`` attribute
-that we can use in our view code:
+Pyramid's :term:`request` object now has a ``session`` attribute that we can
+use in our view code in ``views.py``:
.. literalinclude:: quick_tour/package/hello_world/views.py
- :start-after: Start Sphinx Include 1
- :end-before: End Sphinx Include 1
+ :language: python
+ :linenos:
+ :lineno-start: 9
+ :lines: 9-15
+ :emphasize-lines: 3-7
-With this, each reload will increase the counter displayed in our
-Jinja2 template:
+We need to update our Jinja2 template to show counter increment in the session:
.. literalinclude:: quick_tour/package/hello_world/templates/mytemplate.jinja2
:language: jinja
- :start-after: Start Sphinx Include 1
- :end-before: End Sphinx Include 1
+ :linenos:
+ :lineno-start: 40
+ :lines: 40-42
+ :emphasize-lines: 3
.. seealso:: See also:
- :ref:`Quick Tutorial Sessions <qtut_sessions>`,
- :ref:`sessions_chapter`, :ref:`flash_messages`,
- :ref:`session_module`, and :term:`pyramid_redis_sessions`.
+ :ref:`Quick Tutorial Sessions <qtut_sessions>`, :ref:`sessions_chapter`,
+ :ref:`flash_messages`, :ref:`session_module`, and
+ :term:`pyramid_redis_sessions`.
+
Databases
=========
-Web applications mean data. Data means databases. Frequently SQL
-databases. SQL databases frequently mean an "ORM"
-(object-relational mapper.) In Python, ORM usually leads to the
-mega-quality *SQLAlchemy*, a Python package that greatly eases working
-with databases.
+Web applications mean data. Data means databases. Frequently SQL databases. SQL
+databases frequently mean an "ORM" (object-relational mapper.) In Python, ORM
+usually leads to the mega-quality *SQLAlchemy*, a Python package that greatly
+eases working with databases.
-Pyramid and SQLAlchemy are great friends. That friendship includes a
-scaffold!
+Pyramid and SQLAlchemy are great friends. That friendship includes a scaffold!
.. code-block:: bash
- $ pcreate --scaffold alchemy sqla_demo
+ $ $VENV/bin/pcreate --scaffold alchemy sqla_demo
$ cd sqla_demo
- $ python setup.py develop
+ $ $VENV/bin/pip install -e .
-We now have a working sample SQLAlchemy application with all
-dependencies installed. The sample project provides a console script to
-initialize a SQLite database with tables. Let's run it and then start
-the application:
+We now have a working sample SQLAlchemy application with all dependencies
+installed. The sample project provides a console script to initialize a SQLite
+database with tables. Let's run it, then start the application:
.. code-block:: bash
- $ initialize_sqla_demo_db development.ini
- $ pserve development.ini
+ $ $VENV/bin/initialize_sqla_demo_db development.ini
+ $ $VENV/bin/pserve development.ini
-The ORM eases the mapping of database structures into a programming
-language. SQLAlchemy uses "models" for this mapping. The scaffold
-generated a sample model:
+The ORM eases the mapping of database structures into a programming language.
+SQLAlchemy uses "models" for this mapping. The scaffold generated a sample
+model:
-.. literalinclude:: quick_tour/sqla_demo/sqla_demo/models.py
+.. literalinclude:: quick_tour/sqla_demo/sqla_demo/models/mymodel.py
+ :language: python
:start-after: Start Sphinx Include
:end-before: End Sphinx Include
-View code, which mediates the logic between web requests and the rest
-of the system, can then easily get at the data thanks to SQLAlchemy:
+View code, which mediates the logic between web requests and the rest of the
+system, can then easily get at the data thanks to SQLAlchemy:
-.. literalinclude:: quick_tour/sqla_demo/sqla_demo/views.py
+.. literalinclude:: quick_tour/sqla_demo/sqla_demo/views/default.py
+ :language: python
:start-after: Start Sphinx Include
:end-before: End Sphinx Include
.. seealso:: See also:
- :ref:`Quick Tutorial Databases <qtut_databases>`,
- `SQLAlchemy <http://www.sqlalchemy.org/>`_,
- :ref:`making_a_console_script`,
- :ref:`bfg_sql_wiki_tutorial`, and
- :ref:`Application Transactions With pyramid_tm <tm:overview>`
+ :ref:`Quick Tutorial Databases <qtut_databases>`, `SQLAlchemy
+ <http://www.sqlalchemy.org/>`_, :ref:`making_a_console_script`,
+ :ref:`bfg_sql_wiki_tutorial`, and :ref:`Application Transactions with
+ pyramid_tm <tm:overview>`.
+
Forms
=====
-Developers have lots of opinions about web forms, and thus there are many
-form libraries for Python. Pyramid doesn't directly bundle a form
-library, but *Deform* is a popular choice for forms,
-along with its related *Colander* schema system.
+Developers have lots of opinions about web forms, thus there are many form
+libraries for Python. Pyramid doesn't directly bundle a form library, but
+*Deform* is a popular choice for forms, along with its related *Colander*
+schema system.
-As an example, imagine we want a form that edits a wiki page. The form
-should have two fields on it, one of them a required title and the
-other a rich text editor for the body. With Deform we can express this
-as a Colander schema:
+As an example, imagine we want a form that edits a wiki page. The form should
+have two fields on it, one of them a required title and the other a rich text
+editor for the body. With Deform we can express this as a Colander schema:
.. code-block:: python
@@ -858,8 +937,8 @@ as a Colander schema:
widget=deform.widget.RichTextWidget()
)
-With this in place, we can render the HTML for a form,
-perhaps with form data from an existing page:
+With this in place, we can render the HTML for a form, perhaps with form data
+from an existing page:
.. code-block:: python
@@ -883,20 +962,18 @@ We'd like to handle form submission, validation, and saving:
page['title'] = appstruct['title']
page['body'] = appstruct['body']
-Deform and Colander provide a very flexible combination for forms,
-widgets, schemas, and validation. Recent versions of Deform also
-include a :ref:`retail mode <deform:retail>` for gaining Deform
-features on custom forms.
+Deform and Colander provide a very flexible combination for forms, widgets,
+schemas, and validation. Recent versions of Deform also include a :ref:`retail
+mode <deform:retail>` for gaining Deform features on custom forms.
-Also the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform
-widgets using attractive CSS from Twitter Bootstrap and more powerful widgets
-from Chosen.
+Also the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform widgets
+using attractive CSS from Twitter Bootstrap and more powerful widgets from
+Chosen.
.. seealso:: See also:
- :ref:`Quick Tutorial Forms <qtut_forms>`,
- :ref:`Deform <deform:overview>`,
- :ref:`Colander <colander:overview>`, and
- `deform_bootstrap <https://pypi.python.org/pypi/deform_bootstrap>`_
+ :ref:`Quick Tutorial Forms <qtut_forms>`, :ref:`Deform <deform:overview>`,
+ :ref:`Colander <colander:overview>`, and `deform_bootstrap
+ <https://pypi.python.org/pypi/deform_bootstrap>`_.
Conclusion
==========
diff --git a/docs/quick_tour/awesome/CHANGES.txt b/docs/quick_tour/awesome/CHANGES.txt
deleted file mode 100644
index ffa255da8..000000000
--- a/docs/quick_tour/awesome/CHANGES.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-0.0
----
-
-- Initial version
diff --git a/docs/quick_tour/awesome/MANIFEST.in b/docs/quick_tour/awesome/MANIFEST.in
deleted file mode 100644
index e78395da8..000000000
--- a/docs/quick_tour/awesome/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include *.txt *.ini *.cfg *.rst
-recursive-include awesome *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/quick_tour/awesome/README.txt b/docs/quick_tour/awesome/README.txt
deleted file mode 100644
index f695286d9..000000000
--- a/docs/quick_tour/awesome/README.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-awesome README
-
-
-
diff --git a/docs/quick_tour/awesome/awesome/__init__.py b/docs/quick_tour/awesome/awesome/__init__.py
deleted file mode 100644
index 408033997..000000000
--- a/docs/quick_tour/awesome/awesome/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from pyramid.config import Configurator
-from pyramid_jinja2 import renderer_factory
-from awesome.models import get_root
-
-def main(global_config, **settings):
- """ This function returns a WSGI application.
-
- It is usually called by the PasteDeploy framework during
- ``paster serve``.
- """
- settings = dict(settings)
- settings.setdefault('jinja2.i18n.domain', 'awesome')
-
- config = Configurator(root_factory=get_root, settings=settings)
- config.add_translation_dirs('locale/')
- config.include('pyramid_jinja2')
-
- config.add_static_view('static', 'static')
- config.add_view('awesome.views.my_view',
- context='awesome.models.MyModel',
- renderer="mytemplate.jinja2")
-
- return config.make_wsgi_app()
diff --git a/docs/quick_tour/awesome/awesome/locale/awesome.pot b/docs/quick_tour/awesome/awesome/locale/awesome.pot
deleted file mode 100644
index 9c9460cb2..000000000
--- a/docs/quick_tour/awesome/awesome/locale/awesome.pot
+++ /dev/null
@@ -1,21 +0,0 @@
-# Translations template for PROJECT.
-# Copyright (C) 2011 ORGANIZATION
-# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PROJECT VERSION\n"
-"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-05-12 09:14-0330\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-
-msgid "Hello!"
-msgstr ""
diff --git a/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.mo b/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.mo
deleted file mode 100644
index 40bf0c271..000000000
--- a/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.mo
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.po b/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.po
deleted file mode 100644
index 0df243dba..000000000
--- a/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.po
+++ /dev/null
@@ -1,21 +0,0 @@
-# Translations template for PROJECT.
-# Copyright (C) 2011 ORGANIZATION
-# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PROJECT VERSION\n"
-"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-05-12 09:14-0330\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-
-msgid "Hello!"
-msgstr "Hallo!"
diff --git a/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.mo b/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.mo
deleted file mode 100644
index 4fc438bfe..000000000
--- a/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.mo
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.po b/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.po
deleted file mode 100644
index dc0aae5d7..000000000
--- a/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.po
+++ /dev/null
@@ -1,21 +0,0 @@
-# Translations template for PROJECT.
-# Copyright (C) 2011 ORGANIZATION
-# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PROJECT VERSION\n"
-"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-05-12 09:14-0330\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 0.9.6\n"
-
-msgid "Hello!"
-msgstr "Bonjour!"
diff --git a/docs/quick_tour/awesome/awesome/static/favicon.ico b/docs/quick_tour/awesome/awesome/static/favicon.ico
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/quick_tour/awesome/awesome/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/awesome/awesome/static/logo.png b/docs/quick_tour/awesome/awesome/static/logo.png
deleted file mode 100644
index 88f5d9865..000000000
--- a/docs/quick_tour/awesome/awesome/static/logo.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/awesome/awesome/static/pylons.css b/docs/quick_tour/awesome/awesome/static/pylons.css
deleted file mode 100644
index 42e2e320e..000000000
--- a/docs/quick_tour/awesome/awesome/static/pylons.css
+++ /dev/null
@@ -1,73 +0,0 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-/* remember to define focus styles! */
-:focus{outline:0;}
-/* remember to highlight inserts somehow! */
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-/* tables still need 'cellspacing="0"' in the markup */
-table{border-collapse:collapse;border-spacing:0;}
-/* restyling */
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-/* lists */
-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;}
-/* nested lists have no top/bottom margins */
-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;}
-/* 2 deep unordered lists use a circle */
-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;}
-/* 3 deep (or more) unordered lists use a square */
-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;/* 28px */
-line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;/* 24px */
-line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;/* 20px */
-line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"Nobile","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#144fb2;font-style:normal;}
-#wrap {min-height: 100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;}
-#header{background-color:#e88f00;top:0;font-size:14px;}
-#footer{background-color:#000000;bottom:0;position: relative;margin-top:-40px;clear:both;}
-.header,.footer{width:700px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#bottom{width:100%;}
-#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;}
-#bottom{color:#222;background-color:#ffffff;overflow:auto;padding-bottom:80px;}
-.top,.bottom{width:700px;margin-right:auto;margin-left:auto;}
-.top{padding-top:100px;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:325px;float:left;padding-right:25px;}
-#right{width:325px;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=submit]{background-color:#ddd;font-weight:bold;}
-/*Opera Fix*/
-body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;}
diff --git a/docs/quick_tour/awesome/awesome/templates/mytemplate.jinja2 b/docs/quick_tour/awesome/awesome/templates/mytemplate.jinja2
deleted file mode 100644
index 8bf676041..000000000
--- a/docs/quick_tour/awesome/awesome/templates/mytemplate.jinja2
+++ /dev/null
@@ -1,87 +0,0 @@
-<!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" lang="en">
-<head>
- <title>The Pyramid Web 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.application_url}}/static/favicon.ico" />
- <link rel="stylesheet" href="{{request.application_url}}/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
- <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Nobile:regular,italic,bold,bolditalic&amp;subset=latin" type="text/css" media="screen" charset="utf-8" />
- <!--[if !IE 7]>
- <style type="text/css">
- #wrap {display:table;height:100%}
- </style>
- <![endif]-->
- <style type="text/css">
- .locale { text-align: center; }
- .locale-name { font-weight: bold; }
- </style>
-</head>
-<body>
- <div id="wrap">
- <div id="header">
- <div class="header">The Pyramid Web Framework</div>
- </div>
- <div id="top">
- <div class="top align-center">
- <img src="{{request.application_url}}/static/logo.png" width="300" height="80" alt="Logo"/>
- <p class="app-welcome">
- Welcome to <span class="app-name">{{project}}</span>, an application generated by<br/>
- the Pyramid web framework.
- </p>
- </div>
- </div>
- <div id="bottom">
- <div class="locale">
- <h2>{% trans %}Hello!{% endtrans %}</h2>
- <p>Request performed with <span class="locale-name">{{ request.locale_name }}</span> locale.</p>
- </div>
- <div class="bottom">
- <div id="left" class="align-right">
- <h3>Search Pyramid documentation</h3>
- <form method="get" action="http://docs.pylonshq.com/pyramid/dev/search.html">
- <input type="text" id="q" name="q" value="" />
- <input type="submit" id="x" value="Search" />
- </form>
- </div>
- <div id="right" class="align-left">
- <h3>Pyramid links</h3>
- <ul class="links">
- <li>
- <a href="http://pylonshq.com">Pylons Website</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/">The Pylons Project Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#narrative-documentation">Narrative Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#api-documentation">API Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#tutorials">Tutorials</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#change-history">Change History</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#sample-applications">Sample Applications</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#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">© Copyright 2008-2010, Agendaless Consulting.</div>
- </div>
-</body>
-</html>
diff --git a/docs/quick_tour/awesome/awesome/tests.py b/docs/quick_tour/awesome/awesome/tests.py
deleted file mode 100644
index ac222e25b..000000000
--- a/docs/quick_tour/awesome/awesome/tests.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import unittest
-from pyramid import testing
-from pyramid.i18n import TranslationStringFactory
-
-_ = TranslationStringFactory('awesome')
-
-
-class ViewTests(unittest.TestCase):
-
- def setUp(self):
- testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def test_my_view(self):
- from awesome.views import my_view
- request = testing.DummyRequest()
- response = my_view(request)
- self.assertEqual(response['project'], 'awesome')
-
diff --git a/docs/quick_tour/awesome/awesome/views.py b/docs/quick_tour/awesome/awesome/views.py
deleted file mode 100644
index 67b282f87..000000000
--- a/docs/quick_tour/awesome/awesome/views.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from pyramid.i18n import TranslationStringFactory
-
-_ = TranslationStringFactory('awesome')
-
-def my_view(request):
- return {'project':'awesome'}
diff --git a/docs/quick_tour/awesome/development.ini b/docs/quick_tour/awesome/development.ini
deleted file mode 100644
index a473d32f1..000000000
--- a/docs/quick_tour/awesome/development.ini
+++ /dev/null
@@ -1,49 +0,0 @@
-[app:awesome]
-use = egg:awesome
-reload_templates = true
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = true
-default_locale_name = en
-jinja2.directories = awesome:templates
-
-[pipeline:main]
-pipeline =
- awesome
-
-[server:main]
-use = egg:pyramid#wsgiref
-host = 0.0.0.0
-port = 6543
-
-# Begin logging configuration
-
-[loggers]
-keys = root, awesome
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-[logger_root]
-level = INFO
-handlers = console
-
-[logger_awesome]
-level = DEBUG
-handlers =
-qualname = awesome
-
-[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
diff --git a/docs/quick_tour/awesome/message-extraction.ini b/docs/quick_tour/awesome/message-extraction.ini
deleted file mode 100644
index 0c3d54bc1..000000000
--- a/docs/quick_tour/awesome/message-extraction.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[python: **.py]
-[jinja2: **.jinja2]
-encoding = utf-8
diff --git a/docs/quick_tour/awesome/setup.py b/docs/quick_tour/awesome/setup.py
deleted file mode 100644
index 32d666317..000000000
--- a/docs/quick_tour/awesome/setup.py
+++ /dev/null
@@ -1,36 +0,0 @@
-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>=1.0.2', 'pyramid_jinja2']
-
-setup(name='awesome',
- version='0.0',
- description='awesome',
- long_description=README + '\n\n' + CHANGES,
- classifiers=[
- "Programming Language :: Python",
- "Framework :: Pylons",
- "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="awesome",
- entry_points = """\
- [paste.app_factory]
- main = awesome:main
- """,
- paster_plugins=['pyramid'],
- )
diff --git a/docs/quick_tour/hello_world/app.py b/docs/quick_tour/hello_world/app.py
index df5a6cf18..75d22ac96 100644
--- a/docs/quick_tour/hello_world/app.py
+++ b/docs/quick_tour/hello_world/app.py
@@ -13,4 +13,4 @@ if __name__ == '__main__':
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() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tour/jinja2/app.py b/docs/quick_tour/jinja2/app.py
index 83af219db..b7632807b 100644
--- a/docs/quick_tour/jinja2/app.py
+++ b/docs/quick_tour/jinja2/app.py
@@ -8,4 +8,4 @@ if __name__ == '__main__':
config.scan('views')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
- server.serve_forever() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tour/jinja2/views.py b/docs/quick_tour/jinja2/views.py
index 916cdc720..7dbb45287 100644
--- a/docs/quick_tour/jinja2/views.py
+++ b/docs/quick_tour/jinja2/views.py
@@ -1,8 +1,6 @@
from pyramid.view import view_config
-# Start View 1
@view_config(route_name='hello', renderer='hello_world.jinja2')
-# End View 1
def hello_world(request):
return dict(name=request.matchdict['name'])
diff --git a/docs/quick_tour/json/app.py b/docs/quick_tour/json/app.py
index 950cb478f..40faddd00 100644
--- a/docs/quick_tour/json/app.py
+++ b/docs/quick_tour/json/app.py
@@ -1,8 +1,6 @@
from wsgiref.simple_server import make_server
-
from pyramid.config import Configurator
-
if __name__ == '__main__':
config = Configurator()
config.add_route('hello', '/howdy/{name}')
@@ -12,4 +10,4 @@ if __name__ == '__main__':
config.scan('views')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
- server.serve_forever() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tour/json/hello_world.jinja2 b/docs/quick_tour/json/hello_world.jinja2
index f6862e618..a55865231 100644
--- a/docs/quick_tour/json/hello_world.jinja2
+++ b/docs/quick_tour/json/hello_world.jinja2
@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
- <title>Quick Glance</title>
- <link rel="stylesheet" href="/static/app.css"/>
+ <title>Hello World</title>
+ <link rel="stylesheet" href="{{ request.static_url('__main__:static/app.css') }}"/>
</head>
<body>
<h1>Hello {{ name }}!</h1>
diff --git a/docs/quick_tour/json/hello_world.pt b/docs/quick_tour/json/hello_world.pt
deleted file mode 100644
index 711054aa9..000000000
--- a/docs/quick_tour/json/hello_world.pt
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <title>Quick Glance</title>
- <!-- Start Link 1 -->
- <link rel="stylesheet" href="/static/app.css" />
- <!-- End Link 1 -->
- <!-- Start Link 2 -->
- <link rel="stylesheet"
- href="${request.static_url('static/app.css')}"
- />
- <!-- End Link 2 -->
-</head>
-<body>
-<h1>Hello ${name}!</h1>
-</body>
-</html> \ No newline at end of file
diff --git a/docs/quick_tour/json/views.py b/docs/quick_tour/json/views.py
index 583e220c7..22aa8aad6 100644
--- a/docs/quick_tour/json/views.py
+++ b/docs/quick_tour/json/views.py
@@ -1,13 +1,11 @@
from pyramid.view import view_config
-@view_config(route_name='hello', renderer='hello_world.pt')
+@view_config(route_name='hello', renderer='hello_world.jinja2')
def hello_world(request):
return dict(name=request.matchdict['name'])
-# Start View 1
@view_config(route_name='hello_json', renderer='json')
def hello_json(request):
return [1, 2, 3]
- # End View 1 \ No newline at end of file
diff --git a/docs/quick_tour/package/MANIFEST.in b/docs/quick_tour/package/MANIFEST.in
index 18fbd855c..1d0352f7d 100644
--- a/docs/quick_tour/package/MANIFEST.in
+++ b/docs/quick_tour/package/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.jinja2 *.js *.html *.xml
diff --git a/docs/quick_tour/package/development.ini b/docs/quick_tour/package/development.ini
index a3a73e885..20f9817a9 100644
--- a/docs/quick_tour/package/development.ini
+++ b/docs/quick_tour/package/development.ini
@@ -1,37 +1,41 @@
-# Start Includes
-[app:hello_world]
-pyramid.includes = pyramid_debugtoolbar
-# End Includes
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/narr/environment.html
+###
+
+[app:main]
use = egg:hello_world
-reload_templates = true
-debug_authorization = false
-debug_notfound = false
-debug_routematch = false
-debug_templates = true
-default_locale_name = en
-jinja2.directories = hello_world:templates
-
-[pipeline:main]
-pipeline =
- hello_world
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.debug_templates = true
+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:pyramid#wsgiref
-host = 0.0.0.0
+use = egg:waitress#main
+host = 127.0.0.1
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/narr/logging.html
+###
-# Start Sphinx Include
[loggers]
keys = root, hello_world
-[logger_hello_world]
-level = DEBUG
-handlers =
-qualname = hello_world
-# End Sphinx Include
-
[handlers]
keys = console
@@ -42,6 +46,11 @@ keys = generic
level = INFO
handlers = console
+[logger_hello_world]
+level = DEBUG
+handlers =
+qualname = hello_world
+
[handler_console]
class = StreamHandler
args = (sys.stderr,)
@@ -49,6 +58,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tour/package/hello_world/__init__.py b/docs/quick_tour/package/hello_world/__init__.py
index 4a4fbec30..97f93d5a8 100644
--- a/docs/quick_tour/package/hello_world/__init__.py
+++ b/docs/quick_tour/package/hello_world/__init__.py
@@ -1,10 +1,7 @@
from pyramid.config import Configurator
-from pyramid_jinja2 import renderer_factory
-# Start Sphinx Include 1
+from hello_world.resources import get_root
from pyramid.session import SignedCookieSessionFactory
-# End Sphinx Include 1
-from hello_world.models import get_root
def main(global_config, **settings):
""" This function returns a WSGI application.
@@ -15,20 +12,15 @@ def main(global_config, **settings):
settings = dict(settings)
settings.setdefault('jinja2.i18n.domain', 'hello_world')
- # Start Sphinx Include 2
my_session_factory = SignedCookieSessionFactory('itsaseekreet')
config = Configurator(root_factory=get_root, settings=settings,
session_factory=my_session_factory)
- # End Sphinx Include 2
config.add_translation_dirs('locale/')
- # Start Include
config.include('pyramid_jinja2')
- # End Include
-
config.add_static_view('static', 'static')
config.add_view('hello_world.views.my_view',
- context='hello_world.models.MyModel',
- renderer="mytemplate.jinja2")
+ context='hello_world.resources.MyResource',
+ renderer="templates/mytemplate.jinja2")
return config.make_wsgi_app()
diff --git a/docs/quick_tour/package/hello_world/init.py b/docs/quick_tour/package/hello_world/init.py
deleted file mode 100644
index 5b5f6a118..000000000
--- a/docs/quick_tour/package/hello_world/init.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from pyramid.config import Configurator
-from pyramid_jinja2 import renderer_factory
-# Start Sphinx 1
-from pyramid.session import SignedCookieSessionFactory
-# End Sphinx 1
-
-from hello_world.models import get_root
-
-def main(global_config, **settings):
- """ This function returns a WSGI application.
-
- It is usually called by the PasteDeploy framework during
- ``paster serve``.
- """
- settings = dict(settings)
- settings.setdefault('jinja2.i18n.domain', 'hello_world')
-
- config = Configurator(root_factory=get_root, settings=settings)
- config.add_translation_dirs('locale/')
- # Start Include
- config.include('pyramid_jinja2')
- # End Include
-
- # Start Sphinx Include 2
- my_session_factory = SignedCookieSessionFactory('itsaseekreet')
- config = Configurator(session_factory=my_session_factory)
- # End Sphinx Include 2
-
- config.add_static_view('static', 'static')
- config.add_view('hello_world.views.my_view',
- context='hello_world.models.MyModel',
- renderer="mytemplate.jinja2")
-
- return config.make_wsgi_app()
diff --git a/docs/quick_tour/package/hello_world/models.py b/docs/quick_tour/package/hello_world/models.py
deleted file mode 100644
index edd361c9c..000000000
--- a/docs/quick_tour/package/hello_world/models.py
+++ /dev/null
@@ -1,8 +0,0 @@
-class MyModel(object):
- pass
-
-root = MyModel()
-
-
-def get_root(request):
- return root
diff --git a/docs/quick_tour/awesome/awesome/models.py b/docs/quick_tour/package/hello_world/resources.py
index edd361c9c..e89c2f363 100644
--- a/docs/quick_tour/awesome/awesome/models.py
+++ b/docs/quick_tour/package/hello_world/resources.py
@@ -1,7 +1,7 @@
-class MyModel(object):
+class MyResource(object):
pass
-root = MyModel()
+root = MyResource()
def get_root(request):
diff --git a/docs/quick_tour/package/hello_world/static/logo.png b/docs/quick_tour/package/hello_world/static/logo.png
deleted file mode 100644
index 88f5d9865..000000000
--- a/docs/quick_tour/package/hello_world/static/logo.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/package/hello_world/static/pylons.css b/docs/quick_tour/package/hello_world/static/pylons.css
deleted file mode 100644
index 42e2e320e..000000000
--- a/docs/quick_tour/package/hello_world/static/pylons.css
+++ /dev/null
@@ -1,73 +0,0 @@
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */
-vertical-align:baseline;background:transparent;}
-body{line-height:1;}
-ol,ul{list-style:none;}
-blockquote,q{quotes:none;}
-blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;}
-/* remember to define focus styles! */
-:focus{outline:0;}
-/* remember to highlight inserts somehow! */
-ins{text-decoration:none;}
-del{text-decoration:line-through;}
-/* tables still need 'cellspacing="0"' in the markup */
-table{border-collapse:collapse;border-spacing:0;}
-/* restyling */
-sub{vertical-align:sub;font-size:smaller;line-height:normal;}
-sup{vertical-align:super;font-size:smaller;line-height:normal;}
-/* lists */
-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;}
-/* nested lists have no top/bottom margins */
-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;}
-/* 2 deep unordered lists use a circle */
-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;}
-/* 3 deep (or more) unordered lists use a square */
-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;/* 28px */
-line-height:1.7em;font-family:helvetica,verdana;}
-h2{font-size:1.5em;/* 24px */
-line-height:1.7em;font-family:helvetica,verdana;}
-h3{font-size:1.25em;/* 20px */
-line-height:1.7em;font-family:helvetica,verdana;}
-h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;}
-html,body{width:100%;height:100%;}
-body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;}
-a{color:#1b61d6;text-decoration:none;}
-a:hover{color:#e88f00;text-decoration:underline;}
-body h1,
-body h2,
-body h3,
-body h4,
-body h5,
-body h6{font-family:"Nobile","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#144fb2;font-style:normal;}
-#wrap {min-height: 100%;}
-#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;}
-#header{background-color:#e88f00;top:0;font-size:14px;}
-#footer{background-color:#000000;bottom:0;position: relative;margin-top:-40px;clear:both;}
-.header,.footer{width:700px;margin-right:auto;margin-left:auto;}
-.wrapper{width:100%}
-#top,#bottom{width:100%;}
-#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;}
-#bottom{color:#222;background-color:#ffffff;overflow:auto;padding-bottom:80px;}
-.top,.bottom{width:700px;margin-right:auto;margin-left:auto;}
-.top{padding-top:100px;}
-.app-welcome{margin-top:25px;}
-.app-name{color:#000000;font-weight:bold;}
-.bottom{padding-top:50px;}
-#left{width:325px;float:left;padding-right:25px;}
-#right{width:325px;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=submit]{background-color:#ddd;font-weight:bold;}
-/*Opera Fix*/
-body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;}
diff --git a/docs/quick_tour/package/hello_world/static/pyramid-16x16.png b/docs/quick_tour/package/hello_world/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/quick_tour/package/hello_world/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/quick_tour/package/hello_world/static/pyramid.png b/docs/quick_tour/package/hello_world/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/quick_tour/package/hello_world/static/pyramid.png
Binary files differ
diff --git a/docs/quick_tour/package/hello_world/static/theme.css b/docs/quick_tour/package/hello_world/static/theme.css
new file mode 100644
index 000000000..e3cf3f290
--- /dev/null
+++ b/docs/quick_tour/package/hello_world/static/theme.css
@@ -0,0 +1,153 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a {
+ color: #ffffff;
+}
+.starter-template .links ul li a:hover {
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
+
diff --git a/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2 b/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
index 25a28ed7a..a6089aebc 100644
--- a/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
+++ b/docs/quick_tour/package/hello_world/templates/mytemplate.jinja2
@@ -1,90 +1,72 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
- <title>The Pyramid Web 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.application_url}}/static/favicon.ico" />
- <link rel="stylesheet" href="{{request.application_url}}/static/pylons.css" type="text/css" media="screen" charset="utf-8" />
- <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Nobile:regular,italic,bold,bolditalic&amp;subset=latin" type="text/css" media="screen" charset="utf-8" />
- <!--[if !IE 7]>
- <style type="text/css">
- #wrap {display:table;height:100%}
- </style>
- <![endif]-->
- <style type="text/css">
- .locale { text-align: center; }
- .locale-name { font-weight: bold; }
- </style>
-</head>
-<body>
- <div id="wrap">
- <div id="header">
- <div class="header">The Pyramid Web Framework</div>
- </div>
- <div id="top">
- <div class="top align-center">
- <img src="{{request.application_url}}/static/logo.png" width="300" height="80" alt="Logo"/>
- <p class="app-welcome">
- Welcome to <span class="app-name">{{project}}</span>, an application generated by<br/>
- the Pyramid Web Framework.
- </p>
- </div>
- </div>
- <div id="bottom">
- <div class="locale">
- <h2>{% trans %}Hello!{% endtrans %}</h2>
- <!-- Start Sphinx Include 1 -->
- <p>Counter: {{ request.session.counter }}</p>
- <!-- End Sphinx Include 1 -->
- <p>Request performed with <span class="locale-name">{{ request.locale_name }}</span> locale.</p>
+<!DOCTYPE html>
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="{{request.static_url('hello_world:static/pyramid-16x16.png')}}">
+
+ <title>Starter Scaffold for Pyramid Jinja2</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="{{request.static_url('hello_world:static/theme.css')}}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="{{request.static_url('hello_world:static/pyramid.png')}}" alt="pyramid web framework">
</div>
- <div class="bottom">
- <div id="left" class="align-right">
- <h3>Search Pyramid documentation</h3>
- <form method="get" action="http://docs.pylonshq.com/pyramid/dev/search.html">
- <input type="text" id="q" name="q" value="" />
- <input type="submit" id="x" value="Search" />
- </form>
- </div>
- <div id="right" class="align-left">
- <h3>Pyramid links</h3>
- <ul class="links">
- <li>
- <a href="http://pylonshq.com">Pylons Website</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/">The Pylons Project Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#narrative-documentation">Narrative Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#api-documentation">API Documentation</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#tutorials">Tutorials</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#change-history">Change History</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#sample-applications">Sample Applications</a>
- </li>
- <li>
- <a href="http://docs.pylonshq.com/pyramid/dev/#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">© Copyright 2008-2010, Agendaless Consulting.</div>
- </div>
-</body>
+ <div class="col-md-10">
+ <div class="content">
+ <h1>
+ <span class="font-semi-bold">Pyramid</span>
+ <span class="smaller">Jinja2 scaffold</span>
+ </h1>
+ <p class="lead">
+ {% trans %}Hello{% endtrans %} to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.6</span>.</p>
+ <p>Counter: {{ request.session.counter }}</p>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.6</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.6-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
</html>
diff --git a/docs/quick_tour/package/hello_world/views.py b/docs/quick_tour/package/hello_world/views.py
index 109c260ad..9f7953c8e 100644
--- a/docs/quick_tour/package/hello_world/views.py
+++ b/docs/quick_tour/package/hello_world/views.py
@@ -1,22 +1,16 @@
-# Start Logging 1
+from pyramid.i18n import TranslationStringFactory
+
import logging
log = logging.getLogger(__name__)
-# End Logging 1
-
-from pyramid.i18n import TranslationStringFactory
_ = TranslationStringFactory('hello_world')
def my_view(request):
- # Start Logging 2
log.debug('Some Message')
- # End Logging 2
- # Start Sphinx Include 1
session = request.session
if 'counter' in session:
session['counter'] += 1
else:
session['counter'] = 0
- # End Sphinx Include 1
return {'project': 'hello_world'}
diff --git a/docs/quick_tour/package/setup.cfg b/docs/quick_tour/package/setup.cfg
new file mode 100644
index 000000000..186e796fc
--- /dev/null
+++ b/docs/quick_tour/package/setup.cfg
@@ -0,0 +1,28 @@
+[nosetests]
+match = ^test
+nocapture = 1
+cover-package = hello_world
+with-coverage = 1
+cover-erase = 1
+
+[compile_catalog]
+directory = hello_world/locale
+domain = hello_world
+statistics = true
+
+[extract_messages]
+add_comments = TRANSLATORS:
+output_file = hello_world/locale/hello_world.pot
+width = 80
+mapping_file = message-extraction.ini
+
+[init_catalog]
+domain = hello_world
+input_file = hello_world/locale/hello_world.pot
+output_dir = hello_world/locale
+
+[update_catalog]
+domain = hello_world
+input_file = hello_world/locale/hello_world.pot
+output_dir = hello_world/locale
+previous = true
diff --git a/docs/quick_tour/package/setup.py b/docs/quick_tour/package/setup.py
index f118ed5fb..61ed3c406 100644
--- a/docs/quick_tour/package/setup.py
+++ b/docs/quick_tour/package/setup.py
@@ -3,12 +3,17 @@ import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
-README = open(os.path.join(here, 'README.txt')).read()
-CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
-# Start Requires
-requires = ['pyramid>=1.0.2', 'pyramid_jinja2', 'pyramid_debugtoolbar']
-# End Requires
+requires = [
+ 'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'waitress',
+]
setup(name='hello_world',
version='0.0',
@@ -16,7 +21,7 @@ setup(name='hello_world',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
- "Framework :: Pylons",
+ "Framework :: Pyramid",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
],
@@ -28,14 +33,12 @@ setup(name='hello_world',
include_package_data=True,
zip_safe=False,
install_requires=requires,
- tests_require=requires,
+ tests_require={
+ 'testing': ['nose', 'coverage'],
+ },
test_suite="hello_world",
entry_points="""\
[paste.app_factory]
main = hello_world:main
""",
- paster_plugins=['pyramid'],
- extras_require={
- 'testing': ['nose', ],
- }
-) \ No newline at end of file
+ )
diff --git a/docs/quick_tour/requests/app.py b/docs/quick_tour/requests/app.py
index 7ac81eb50..f55264cff 100644
--- a/docs/quick_tour/requests/app.py
+++ b/docs/quick_tour/requests/app.py
@@ -21,4 +21,4 @@ if __name__ == '__main__':
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() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tour/routing/app.py b/docs/quick_tour/routing/app.py
index 04a8a6344..12b547bfe 100644
--- a/docs/quick_tour/routing/app.py
+++ b/docs/quick_tour/routing/app.py
@@ -3,9 +3,7 @@ from pyramid.config import Configurator
if __name__ == '__main__':
config = Configurator()
- # Start Route 1
config.add_route('hello', '/howdy/{first}/{last}')
- # End Route 1
config.scan('views')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
diff --git a/docs/quick_tour/routing/views.py b/docs/quick_tour/routing/views.py
index 8cb8d3780..8a3bd230e 100644
--- a/docs/quick_tour/routing/views.py
+++ b/docs/quick_tour/routing/views.py
@@ -2,9 +2,7 @@ from pyramid.response import Response
from pyramid.view import view_config
-# Start Route 1
@view_config(route_name='hello')
def hello_world(request):
body = '<h1>Hi %(first)s %(last)s!</h1>' % request.matchdict
return Response(body)
- # End Route 1 \ No newline at end of file
diff --git a/docs/quick_tour/sqla_demo/README.txt b/docs/quick_tour/sqla_demo/README.txt
index f35d3aec5..b6d4c7798 100644
--- a/docs/quick_tour/sqla_demo/README.txt
+++ b/docs/quick_tour/sqla_demo/README.txt
@@ -6,9 +6,9 @@ Getting Started
- cd <directory containing this file>
-- $venv/bin/python setup.py develop
+- $VENV/bin/pip install -e .
-- $venv/bin/initialize_sqla_demo_db development.ini
+- $VENV/bin/initialize_sqla_demo_db development.ini
-- $venv/bin/pserve development.ini
+- $VENV/bin/pserve development.ini
diff --git a/docs/quick_tour/sqla_demo/development.ini b/docs/quick_tour/sqla_demo/development.ini
index 174468abf..0db0950a0 100644
--- a/docs/quick_tour/sqla_demo/development.ini
+++ b/docs/quick_tour/sqla_demo/development.ini
@@ -27,7 +27,7 @@ sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tour/sqla_demo/production.ini b/docs/quick_tour/sqla_demo/production.ini
index dc0ba304f..38f3b6318 100644
--- a/docs/quick_tour/sqla_demo/production.ini
+++ b/docs/quick_tour/sqla_demo/production.ini
@@ -59,4 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/quick_tour/sqla_demo/setup.cfg b/docs/quick_tour/sqla_demo/setup.cfg
new file mode 100644
index 000000000..9f91cd122
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/setup.cfg
@@ -0,0 +1,27 @@
+[nosetests]
+match=^test
+nocapture=1
+cover-package=sqla_demo
+with-coverage=1
+cover-erase=1
+
+[compile_catalog]
+directory = sqla_demo/locale
+domain = sqla_demo
+statistics = true
+
+[extract_messages]
+add_comments = TRANSLATORS:
+output_file = sqla_demo/locale/sqla_demo.pot
+width = 80
+
+[init_catalog]
+domain = sqla_demo
+input_file = sqla_demo/locale/sqla_demo.pot
+output_dir = sqla_demo/locale
+
+[update_catalog]
+domain = sqla_demo
+input_file = sqla_demo/locale/sqla_demo.pot
+output_dir = sqla_demo/locale
+previous = true
diff --git a/docs/quick_tour/sqla_demo/setup.py b/docs/quick_tour/sqla_demo/setup.py
index ac2eed035..312a97c06 100644
--- a/docs/quick_tour/sqla_demo/setup.py
+++ b/docs/quick_tour/sqla_demo/setup.py
@@ -3,15 +3,18 @@ import os
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
-README = open(os.path.join(here, 'README.txt')).read()
-CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
requires = [
'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
'SQLAlchemy',
'transaction',
- 'pyramid_tm',
- 'pyramid_debugtoolbar',
'zope.sqlalchemy',
'waitress',
]
diff --git a/docs/quick_tour/sqla_demo/sqla_demo.sqlite b/docs/quick_tour/sqla_demo/sqla_demo.sqlite
deleted file mode 100644
index fa6adb104..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo.sqlite
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
index aac7c5e69..7994bbfa8 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/__init__.py
@@ -1,19 +1,12 @@
from pyramid.config import Configurator
-from sqlalchemy import engine_from_config
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
config = Configurator(settings=settings)
+ config.include('pyramid_jinja2')
+ config.include('.models.meta')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models.py b/docs/quick_tour/sqla_demo/sqla_demo/models.py
deleted file mode 100644
index 3dfb40e58..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/models.py
+++ /dev/null
@@ -1,29 +0,0 @@
-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()
-
-# Start Sphinx Include
-class MyModel(Base):
- __tablename__ = 'models'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- value = Column(Integer)
-
- def __init__(self, name, value):
- self.name = name
- self.value = value
- # End Sphinx Include
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
new file mode 100644
index 000000000..76e0fd26b
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/__init__.py
@@ -0,0 +1,7 @@
+from sqlalchemy.orm import configure_mappers
+# import all models classes here for sqlalchemy mappers
+# to pick up
+from .mymodel import MyModel # noqa
+
+# run configure mappers to ensure we avoid any race conditions
+configure_mappers()
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py
new file mode 100644
index 000000000..03c50ae93
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/meta.py
@@ -0,0 +1,49 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.schema import MetaData
+import zope.sqlalchemy
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
+
+
+def includeme(config):
+ settings = config.get_settings()
+ dbmaker = get_dbmaker(get_engine(settings))
+
+ config.add_request_method(
+ lambda r: get_session(r.tm, dbmaker),
+ 'dbsession',
+ reify=True
+ )
+
+ config.include('pyramid_tm')
+
+
+def get_session(transaction_manager, dbmaker):
+ dbsession = dbmaker()
+ zope.sqlalchemy.register(dbsession,
+ transaction_manager=transaction_manager)
+ return dbsession
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_dbmaker(engine):
+ dbmaker = sessionmaker()
+ dbmaker.configure(bind=engine)
+ return dbmaker
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py b/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py
new file mode 100644
index 000000000..eb645bfe6
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/models/mymodel.py
@@ -0,0 +1,19 @@
+from .meta import Base
+from sqlalchemy import (
+ Column,
+ Index,
+ Integer,
+ Text,
+)
+
+
+# Start Sphinx Include
+class MyModel(Base):
+ __tablename__ = 'models'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text)
+ value = Column(Integer)
+ # End Sphinx Include
+
+
+Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py b/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py
index 66feb3008..f0d09729e 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py
@@ -2,36 +2,44 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
-from ..models import (
- DBSession,
- MyModel,
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import (
Base,
+ get_session,
+ get_engine,
+ get_dbmaker,
)
+from ..models.mymodel import MyModel
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
+ options = parse_vars(argv[2:])
setup_logging(config_uri)
- settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
+ dbmaker = get_dbmaker(engine)
+
+ dbsession = get_session(transaction.manager, dbmaker)
+
Base.metadata.create_all(engine)
+
with transaction.manager:
model = MyModel(name='one', value=1)
- DBSession.add(model)
+ dbsession.add(model)
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/favicon.ico b/docs/quick_tour/sqla_demo/sqla_demo/static/favicon.ico
deleted file mode 100644
index 71f837c9e..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/favicon.ico
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/footerbg.png b/docs/quick_tour/sqla_demo/sqla_demo/static/footerbg.png
deleted file mode 100644
index 1fbc873da..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/footerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/headerbg.png b/docs/quick_tour/sqla_demo/sqla_demo/static/headerbg.png
deleted file mode 100644
index 0596f2020..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/headerbg.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/ie6.css b/docs/quick_tour/sqla_demo/sqla_demo/static/ie6.css
deleted file mode 100644
index b7c8493d8..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/ie6.css
+++ /dev/null
@@ -1,8 +0,0 @@
-* html img,
-* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none",
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')",
-this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''),
-this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')",
-this.runtimeStyle.backgroundImage = "none")),this.pngSet=true)
-);}
-#wrap{display:table;height:100%}
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/middlebg.png b/docs/quick_tour/sqla_demo/sqla_demo/static/middlebg.png
deleted file mode 100644
index 2369cfb7d..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/middlebg.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/pylons.css b/docs/quick_tour/sqla_demo/sqla_demo/static/pylons.css
deleted file mode 100644
index 4b1c017cd..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/pylons.css
+++ /dev/null
@@ -1,372 +0,0 @@
-html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td
-{
- margin: 0;
- padding: 0;
- border: 0;
- outline: 0;
- font-size: 100%; /* 16px */
- vertical-align: baseline;
- background: transparent;
-}
-
-body
-{
- line-height: 1;
-}
-
-ol, ul
-{
- list-style: none;
-}
-
-blockquote, q
-{
- quotes: none;
-}
-
-blockquote:before, blockquote:after, q:before, q:after
-{
- content: '';
- content: none;
-}
-
-:focus
-{
- outline: 0;
-}
-
-ins
-{
- text-decoration: none;
-}
-
-del
-{
- text-decoration: line-through;
-}
-
-table
-{
- border-collapse: collapse;
- border-spacing: 0;
-}
-
-sub
-{
- vertical-align: sub;
- font-size: smaller;
- line-height: normal;
-}
-
-sup
-{
- vertical-align: super;
- font-size: smaller;
- line-height: normal;
-}
-
-ul, menu, dir
-{
- display: block;
- list-style-type: disc;
- margin: 1em 0;
- padding-left: 40px;
-}
-
-ol
-{
- display: block;
- list-style-type: decimal-leading-zero;
- margin: 1em 0;
- padding-left: 40px;
-}
-
-li
-{
- display: list-item;
-}
-
-ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl
-{
- margin-top: 0;
- margin-bottom: 0;
-}
-
-ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir
-{
- list-style-type: circle;
-}
-
-ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir
-{
- list-style-type: square;
-}
-
-.hidden
-{
- display: none;
-}
-
-p
-{
- line-height: 1.5em;
-}
-
-h1
-{
- font-size: 1.75em;
- line-height: 1.7em;
- font-family: helvetica, verdana;
-}
-
-h2
-{
- font-size: 1.5em;
- line-height: 1.7em;
- font-family: helvetica, verdana;
-}
-
-h3
-{
- font-size: 1.25em;
- line-height: 1.7em;
- font-family: helvetica, verdana;
-}
-
-h4
-{
- font-size: 1em;
- line-height: 1.7em;
- font-family: helvetica, verdana;
-}
-
-html, body
-{
- width: 100%;
- height: 100%;
-}
-
-body
-{
- margin: 0;
- padding: 0;
- background-color: #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;
-}
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-16x16.png b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-small.png b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-small.png
deleted file mode 100644
index a5bc0ade7..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-small.png
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png
index 347e05549..4ab837be9 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png
+++ b/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png
Binary files differ
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/theme.css b/docs/quick_tour/sqla_demo/sqla_demo/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/static/transparent.gif b/docs/quick_tour/sqla_demo/sqla_demo/static/transparent.gif
deleted file mode 100644
index 0341802e5..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/static/transparent.gif
+++ /dev/null
Binary files differ
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2
index c9b0cec21..76a098122 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/layout.jinja2
@@ -1,12 +1,12 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('sqla_demo:static/pyramid-16x16.png')}}">
<title>Alchemy Scaffold for The Pyramid Web Framework</title>
@@ -14,7 +14,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('sqla_demo:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('sqla_demo:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7.dev0</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2 b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2
new file mode 100644
index 000000000..bb622bf5a
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7.dev0</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt b/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt
deleted file mode 100644
index 321c0f5fb..000000000
--- a/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt
+++ /dev/null
@@ -1,76 +0,0 @@
-<!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 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('sqla_demo:static/favicon.ico')}" />
- <link rel="stylesheet" href="${request.static_url('sqla_demo: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('sqla_demo: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('sqla_demo: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 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>
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/tests.py b/docs/quick_tour/sqla_demo/sqla_demo/tests.py
index 6fef6d695..b6b6fdf4d 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/tests.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/tests.py
@@ -3,31 +3,63 @@ import transaction
from pyramid import testing
-from .models import DBSession
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
-class TestMyView(unittest.TestCase):
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- from sqlalchemy import create_engine
- engine = create_engine('sqlite://')
- from .models import (
- Base,
- MyModel,
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models.meta')
+ settings = self.config.get_settings()
+
+ from .models.meta import (
+ get_session,
+ get_engine,
+ get_dbmaker,
)
- DBSession.configure(bind=engine)
- Base.metadata.create_all(engine)
- with transaction.manager:
- model = MyModel(name='one', value=55)
- DBSession.add(model)
+
+ self.engine = get_engine(settings)
+ dbmaker = get_dbmaker(self.engine)
+
+ self.session = get_session(transaction.manager, dbmaker)
+
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- DBSession.remove()
+ from .models.meta import Base
+
testing.tearDown()
+ transaction.abort()
+ Base.metadata.create_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def test_it(self):
- from .views import my_view
- request = testing.DummyRequest()
- info = my_view(request)
+ def setUp(self):
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
+
+ from .models.mymodel import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
self.assertEqual(info['project'], 'sqla_demo')
+
+
+class TestMyViewFailureCondition(BaseTest):
+
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/views/__init__.py b/docs/quick_tour/sqla_demo/sqla_demo/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/quick_tour/sqla_demo/sqla_demo/views/__init__.py
diff --git a/docs/quick_tour/sqla_demo/sqla_demo/views.py b/docs/quick_tour/sqla_demo/sqla_demo/views/default.py
index 768a7e42e..e5e70cf9d 100644
--- a/docs/quick_tour/sqla_demo/sqla_demo/views.py
+++ b/docs/quick_tour/sqla_demo/sqla_demo/views/default.py
@@ -3,28 +3,27 @@ from pyramid.view import view_config
from sqlalchemy.exc import DBAPIError
-from .models import (
- DBSession,
- MyModel,
- )
+from ..models.mymodel import MyModel
-@view_config(route_name='home', renderer='templates/mytemplate.pt')
+@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
def my_view(request):
try:
+ query = request.dbsession.query(MyModel)
# Start Sphinx Include
- one = DBSession.query(MyModel).filter(MyModel.name == 'one').first()
+ one = query.filter(MyModel.name == 'one').first()
# End Sphinx Include
except DBAPIError:
- return Response(conn_err_msg, content_type='text/plain', status_int=500)
+ return Response(db_err_msg, content_type='text/plain', status_int=500)
return {'one': one, 'project': 'sqla_demo'}
-conn_err_msg = """\
+
+db_err_msg = """\
Pyramid is having a problem using your SQL database. The problem
might be caused by one of the following things:
1. You may need to run the "initialize_sqla_demo_db" script
- to initialize your database tables. Check your virtual
+ to initialize your database tables. Check your virtual
environment's "bin" directory for this script and try to run it.
2. Your database server may not be running. Check that the
@@ -34,4 +33,3 @@ might be caused by one of the following things:
After you fix the problem, please restart the Pyramid application to
try it again.
"""
-
diff --git a/docs/quick_tour/static_assets/app.py b/docs/quick_tour/static_assets/app.py
index 9c808972f..1849c0a5a 100644
--- a/docs/quick_tour/static_assets/app.py
+++ b/docs/quick_tour/static_assets/app.py
@@ -4,11 +4,9 @@ from pyramid.config import Configurator
if __name__ == '__main__':
config = Configurator()
config.add_route('hello', '/howdy/{name}')
- # Start Static 1
config.add_static_view(name='static', path='static')
- # End Static 1
config.include('pyramid_jinja2')
config.scan('views')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
- server.serve_forever() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tour/static_assets/hello_world.jinja2 b/docs/quick_tour/static_assets/hello_world.jinja2
index f6862e618..a55865231 100644
--- a/docs/quick_tour/static_assets/hello_world.jinja2
+++ b/docs/quick_tour/static_assets/hello_world.jinja2
@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head>
- <title>Quick Glance</title>
- <link rel="stylesheet" href="/static/app.css"/>
+ <title>Hello World</title>
+ <link rel="stylesheet" href="{{ request.static_url('__main__:static/app.css') }}"/>
</head>
<body>
<h1>Hello {{ name }}!</h1>
diff --git a/docs/quick_tour/static_assets/hello_world.pt b/docs/quick_tour/static_assets/hello_world.pt
deleted file mode 100644
index 1797146eb..000000000
--- a/docs/quick_tour/static_assets/hello_world.pt
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html lang="en"><head>
- <title>Quick Glance</title>
- <!-- Start Link 1 -->
- <link rel="stylesheet" href="/static/app.css" />
- <!-- End Link 1 -->
- <!-- Start Link 2 -->
- <link rel="stylesheet"
- href="${request.static_url('static/app.css')}"
- />
- <!-- End Link 2 -->
-</head>
-<body>
-<h1>Hello ${name}!</h1>
-</body>
-</html> \ No newline at end of file
diff --git a/docs/quick_tour/static_assets/hello_world_static.jinja2 b/docs/quick_tour/static_assets/hello_world_static.jinja2
new file mode 100644
index 000000000..0fb2ce296
--- /dev/null
+++ b/docs/quick_tour/static_assets/hello_world_static.jinja2
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <title>Hello World</title>
+ <link rel="stylesheet" href="/static/app.css"/>
+</head>
+<body>
+<h1>Hello {{ name }}!</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/quick_tour/static_assets/views.py b/docs/quick_tour/static_assets/views.py
index 90730ae32..7dbb45287 100644
--- a/docs/quick_tour/static_assets/views.py
+++ b/docs/quick_tour/static_assets/views.py
@@ -1,6 +1,6 @@
from pyramid.view import view_config
-@view_config(route_name='hello', renderer='hello_world.pt')
+@view_config(route_name='hello', renderer='hello_world.jinja2')
def hello_world(request):
return dict(name=request.matchdict['name'])
diff --git a/docs/quick_tour/templating/app.py b/docs/quick_tour/templating/app.py
index 6d1a29f4e..52b7faf55 100644
--- a/docs/quick_tour/templating/app.py
+++ b/docs/quick_tour/templating/app.py
@@ -4,7 +4,8 @@ from pyramid.config import Configurator
if __name__ == '__main__':
config = Configurator()
config.add_route('hello', '/howdy/{name}')
+ config.include('pyramid_chameleon')
config.scan('views')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
- server.serve_forever() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tour/templating/views.py b/docs/quick_tour/templating/views.py
index 6c7846efa..90730ae32 100644
--- a/docs/quick_tour/templating/views.py
+++ b/docs/quick_tour/templating/views.py
@@ -1,8 +1,6 @@
from pyramid.view import view_config
-# Start View 1
@view_config(route_name='hello', renderer='hello_world.pt')
def hello_world(request):
return dict(name=request.matchdict['name'])
- # End View 1 \ No newline at end of file
diff --git a/docs/quick_tour/view_classes/app.py b/docs/quick_tour/view_classes/app.py
index 468c8c29e..40faddd00 100644
--- a/docs/quick_tour/view_classes/app.py
+++ b/docs/quick_tour/view_classes/app.py
@@ -3,11 +3,11 @@ from pyramid.config import Configurator
if __name__ == '__main__':
config = Configurator()
- # Start Routes 1
config.add_route('hello', '/howdy/{name}')
- # End Routes 1
+ config.add_route('hello_json', 'hello.json')
+ config.add_static_view(name='static', path='static')
config.include('pyramid_jinja2')
config.scan('views')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
- server.serve_forever() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tour/view_classes/hello.jinja2 b/docs/quick_tour/view_classes/hello.jinja2
index 3446b96ce..fc3058067 100644
--- a/docs/quick_tour/view_classes/hello.jinja2
+++ b/docs/quick_tour/view_classes/hello.jinja2
@@ -5,13 +5,11 @@
</head>
<body>
<h1>Hello {{ view.name }}!</h1>
-<!-- Start Form 1 -->
<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"/>
+ <input name="new_name">
+ <input type="submit" name="form.edit" value="Save">
+ <input type="submit" name="form.delete" value="Delete">
</form>
-<!-- End Form 1 -->
</body>
</html> \ No newline at end of file
diff --git a/docs/quick_tour/view_classes/views.py b/docs/quick_tour/view_classes/views.py
index 62556142e..10ff238c7 100644
--- a/docs/quick_tour/view_classes/views.py
+++ b/docs/quick_tour/view_classes/views.py
@@ -4,7 +4,6 @@ from pyramid.view import (
)
-# Start View 1
# One route, at /howdy/amy, so don't repeat on each @view_config
@view_defaults(route_name='hello')
class HelloWorldViews:
@@ -29,4 +28,3 @@ class HelloWorldViews:
def delete_view(self):
print('Deleted')
return dict()
- # End View 1 \ No newline at end of file
diff --git a/docs/quick_tour/views/app.py b/docs/quick_tour/views/app.py
index 54dc9ed4b..e8df6eff2 100644
--- a/docs/quick_tour/views/app.py
+++ b/docs/quick_tour/views/app.py
@@ -10,4 +10,4 @@ if __name__ == '__main__':
config.scan('views')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
- server.serve_forever() \ No newline at end of file
+ server.serve_forever()
diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst
index 7fd8173d4..acff97f3b 100644
--- a/docs/quick_tutorial/authentication.rst
+++ b/docs/quick_tutorial/authentication.rst
@@ -1,29 +1,30 @@
+.. _qtut_authentication:
+
==============================
20: Logins With Authentication
==============================
-Login views that authenticate a username/password against a list of
-users.
+Login views that authenticate a username and 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.
+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 storage.
+
+In the next step we will introduce protection of resources with authorization
+security statements.
-In the next step we will introduce protection resources with
-authorization security statements.
Objectives
==========
-- Introduce the Pyramid concepts of authentication
+- Introduce the Pyramid concepts of authentication.
-- Create login/logout views
+- Create login and logout views.
Steps
=====
@@ -33,25 +34,23 @@ Steps
.. code-block:: bash
$ cd ..; cp -r view_classes authentication; cd authentication
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. Put the security hash in the ``authentication/development.ini``
- configuration file as ``tutorial.secret`` instead of putting it in
- the code:
+ 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``:
+#. 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*:
+#. Create an ``authentication/tutorial/security.py`` module that can find our
+ user information by providing an *authentication policy callback*:
.. literalinclude:: authentication/tutorial/security.py
:linenos:
@@ -67,7 +66,7 @@ Steps
:language: html
:linenos:
-#. Provide a login/logout box in ``authentication/tutorial/home.pt``
+#. Provide a login/logout box in ``authentication/tutorial/home.pt``:
.. literalinclude:: authentication/tutorial/home.pt
:language: html
@@ -93,39 +92,37 @@ Steps
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 <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
+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
+<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`` request, it
+returned a login form. When reached via ``POST``, it processed the submitted
+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?
-#. Once I am logged in, does any user-centric information get jammed
- onto each request? Use ``import pdb; pdb.set_trace()`` to answer
- this.
+#. 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:: See also :ref:`security_chapter`,
:ref:`AuthTktAuthenticationPolicy <authentication_module>`.
diff --git a/docs/quick_tutorial/authorization.rst b/docs/quick_tutorial/authorization.rst
index 855043f7f..58c1d2582 100644
--- a/docs/quick_tutorial/authorization.rst
+++ b/docs/quick_tutorial/authorization.rst
@@ -1,34 +1,38 @@
+.. _qtut_authorization:
+
===========================================
21: Protecting Resources With Authorization
===========================================
-Assign security statements to resources describing the permissions
-required to perform an operation.
+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.
+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)
+- 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
+- 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
+- Assign security statements to our root resource.
-- Add a permissions predicate on a view
+- Add a permissions predicate on a view.
+
+- Provide a :term:`Forbidden view` to handle visiting a URL without adequate
+ permissions.
-- Provide a :term:`Forbidden view` to handle visiting a URL without
- adequate permissions
Steps
=====
@@ -38,16 +42,15 @@ Steps
.. code-block:: bash
$ cd ..; cp -r authentication authorization; cd authorization
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
-#. Start by changing ``authorization/tutorial/__init__.py`` to
- specify a root factory to the :term:`configurator`:
+#. Start by changing ``authorization/tutorial/__init__.py`` to specify a root
+ factory to the :term:`configurator`:
.. literalinclude:: authorization/tutorial/__init__.py
:linenos:
-#. That means we need to implement
- ``authorization/tutorial/resources.py``
+#. That means we need to implement ``authorization/tutorial/resources.py``:
.. literalinclude:: authorization/tutorial/resources.py
:linenos:
@@ -68,48 +71,47 @@ Steps
#. 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.
+#. 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``)
+- A view can require a *permission* (``edit``).
-- The context for our view (the ``Root``) has an access control list
- (ACL)
+- 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*
+- 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``)
+- The registered ``groupfinder`` answers whether a particular user (``editor``)
+ has a particular group (``group:editors``).
-In summary: ``hello`` wants ``edit`` permission, ``Root`` says
+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.
+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 ``/howdy``, 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``.
-If you are not logged in and visit ``/howdy``, 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
+Extra credit
============
-#. Do I have to put a ``renderer`` in my ``@forbidden_view_config``
- decorator?
+#. Do I have to put a ``renderer`` in my ``@forbidden_view_config`` decorator?
#. Perhaps you would like the 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?
+#. 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?
+#. 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?
diff --git a/docs/quick_tutorial/conf.py b/docs/quick_tutorial/conf.py
deleted file mode 100644
index 47b8fae41..000000000
--- a/docs/quick_tutorial/conf.py
+++ /dev/null
@@ -1,281 +0,0 @@
-# -*- 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),
-}
diff --git a/docs/quick_tutorial/databases.rst b/docs/quick_tutorial/databases.rst
index 19dfd066d..c8d87c180 100644
--- a/docs/quick_tutorial/databases.rst
+++ b/docs/quick_tutorial/databases.rst
@@ -4,37 +4,39 @@
19: Databases Using SQLAlchemy
==============================
-Store/retrieve data using the SQLAlchemy ORM atop the SQLite database.
+Store and 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.
+Our Pyramid-based wiki application now needs database-backed storage of pages.
+This frequently means an 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.
+In this step we hook up SQLAlchemy to a SQLite database table, providing
+storage and retrieval for the wiki pages 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.
+ The ``alchemy`` scaffold is really helpful for getting an 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
+- Store pages in SQLite by using SQLAlchemy models.
-- Use SQLAlchemy queries to list/add/view/edit pages
+- 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.
-- Provide a database-initialize command by writing a Pyramid *console
- script* which can be run from the command line
Steps
=====
@@ -45,31 +47,31 @@ Steps
$ 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:
+#. 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 ``$VENV/bin/python setup.py develop`` as we
- will change it later.
+ We aren't yet doing ``$VENV/bin/pip install -e .`` as we will change it
+ later.
-#. Our configuration file at ``databases/development.ini`` wires
- together some new pieces:
+#. 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``:
+#. 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:
+#. Make a command-line script at ``databases/tutorial/initialize_db.py`` to
+ initialize the database:
.. literalinclude:: databases/tutorial/initialize_db.py
:linenos:
@@ -78,7 +80,7 @@ Steps
.. code-block:: bash
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. The script references some models in ``databases/tutorial/models.py``:
@@ -90,51 +92,49 @@ Steps
.. code-block:: bash
$ $VENV/bin/initialize_tutorial_db development.ini
- 2015-06-01 11:22:52,650 INFO [sqlalchemy.engine.base.Engine][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
- 2015-06-01 11:22:52,650 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
- 2015-06-01 11:22:52,651 INFO [sqlalchemy.engine.base.Engine][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
- 2015-06-01 11:22:52,651 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
- 2015-06-01 11:22:52,652 INFO [sqlalchemy.engine.base.Engine][MainThread] PRAGMA table_info("wikipages")
- 2015-06-01 11:22:52,652 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
- 2015-06-01 11:22:52,653 INFO [sqlalchemy.engine.base.Engine][MainThread]
+
+ 2016-04-16 13:01:33,055 INFO [sqlalchemy.engine.base.Engine][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
+ 2016-04-16 13:01:33,055 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
+ 2016-04-16 13:01:33,056 INFO [sqlalchemy.engine.base.Engine][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
+ 2016-04-16 13:01:33,056 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
+ 2016-04-16 13:01:33,057 INFO [sqlalchemy.engine.base.Engine][MainThread] PRAGMA table_info("wikipages")
+ 2016-04-16 13:01:33,057 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
+ 2016-04-16 13:01:33,058 INFO [sqlalchemy.engine.base.Engine][MainThread]
CREATE TABLE wikipages (
- uid INTEGER NOT NULL,
- title TEXT,
- body TEXT,
- PRIMARY KEY (uid),
- UNIQUE (title)
+ uid INTEGER NOT NULL,
+ title TEXT,
+ body TEXT,
+ PRIMARY KEY (uid),
+ UNIQUE (title)
)
- 2015-06-01 11:22:52,653 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
- 2015-06-01 11:22:52,655 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT
- 2015-06-01 11:22:52,658 INFO [sqlalchemy.engine.base.Engine][MainThread] BEGIN (implicit)
- 2015-06-01 11:22:52,659 INFO [sqlalchemy.engine.base.Engine][MainThread] INSERT INTO wikipages (title, body) VALUES (?, ?)
- 2015-06-01 11:22:52,659 INFO [sqlalchemy.engine.base.Engine][MainThread] ('Root', '<p>Root</p>')
- 2015-06-01 11:22:52,659 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT
+ 2016-04-16 13:01:33,058 INFO [sqlalchemy.engine.base.Engine][MainThread] ()
+ 2016-04-16 13:01:33,059 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT
+ 2016-04-16 13:01:33,062 INFO [sqlalchemy.engine.base.Engine][MainThread] BEGIN (implicit)
+ 2016-04-16 13:01:33,062 INFO [sqlalchemy.engine.base.Engine][MainThread] INSERT INTO wikipages (title, body) VALUES (?, ?)
+ 2016-04-16 13:01:33,063 INFO [sqlalchemy.engine.base.Engine][MainThread] ('Root', '<p>Root</p>')
+ 2016-04-16 13:01:33,063 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT
-#. With our data now driven by SQLAlchemy queries, we need to update
- our ``databases/tutorial/views.py``:
+#. With our data now driven by SQLAlchemy queries, we need to update our
+ ``databases/tutorial/views.py``:
.. literalinclude:: databases/tutorial/views.py
:linenos:
-#. Our tests in ``databases/tutorial/tests.py`` changed to include
- SQLAlchemy bootstrapping:
+#. 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``:
+#. Run the tests in your package using ``py.test``:
- .. code-block:: bash
-
- $ $VENV/bin/nosetests tutorial
- ..
- -----------------------------------------------------------------
- Ran 2 tests in 1.141s
+ .. code-block:: bash
- OK
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ..
+ 2 passed in 1.41 seconds
#. Run your Pyramid application with:
@@ -144,57 +144,55 @@ Steps
#. 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?
+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
-application either as middleware or a Pyramid "tween". Then,
-just before you return the response, all transaction-aware parts of
-your application are executed.
+Specifically, you can install a transaction manager into your 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.
+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.
+``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``.
+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 virtual environment'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 of 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.
+dictionary-of-dictionaries data with proper database support: list the rows,
+add a row, edit a row, and delete a row.
+
-Extra Credit
+Extra credit
============
-#. Why all this code? Why can't I just type 2 lines and have magic ensue?
+#. Why all this code? Why can't I just type two lines and have magic ensue?
#. Give a try at a button that deletes a wiki page.
diff --git a/docs/quick_tutorial/databases/sqltutorial.sqlite b/docs/quick_tutorial/databases/sqltutorial.sqlite
deleted file mode 100644
index b8bd856fd..000000000
--- a/docs/quick_tutorial/databases/sqltutorial.sqlite
+++ /dev/null
Binary files differ
diff --git a/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
index 01955ef72..d1fea0d7f 100644
--- a/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
+++ b/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt
@@ -4,12 +4,10 @@
<title>WikiPage: Add/Edit</title>
<tal:block tal:repeat="reqt view.reqts['css']">
<link rel="stylesheet" type="text/css"
- href="${request.static_url(reqt)}">
+ href="${request.static_url('deform:static/' + reqt)}"/>
</tal:block>
- <script type="text/javascript"
- src="${request.static_url('deform:static/scripts/jquery-2.0.3.min.js')}"></script>
<tal:block tal:repeat="reqt view.reqts['js']">
- <script src="${request.static_url(reqt)}"
+ <script src="${request.static_url('deform:static/' + reqt)}"
type="text/javascript"></script>
</tal:block>
</head>
diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst
index f11abc493..b02363d40 100644
--- a/docs/quick_tutorial/debugtoolbar.rst
+++ b/docs/quick_tutorial/debugtoolbar.rst
@@ -4,40 +4,42 @@
04: Easier Development with ``debugtoolbar``
============================================
-Error-handling and introspection using the ``pyramid_debugtoolbar``
-add-on.
+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.
+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.
-``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
+- Install and enable the toolbar to help during development.
+
+- Explain Pyramid add-ons.
-- Explain Pyramid add-ons
+- Show how an add-on gets configured into your application.
-- 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:
+#. First we copy the results of the previous step, as well as install the
+ ``pyramid_debugtoolbar`` package:
.. code-block:: bash
$ cd ..; cp -r ini debugtoolbar; cd debugtoolbar
- $ $VENV/bin/python setup.py develop
- $ $VENV/bin/easy_install pyramid_debugtoolbar
+ $ $VENV/bin/pip install -e .
+ $ $VENV/bin/pip install pyramid_debugtoolbar
#. Our ``debugtoolbar/development.ini`` gets a configuration entry for
``pyramid.includes``:
@@ -55,6 +57,7 @@ Steps
#. Open http://localhost:6543/ in your browser. See the handy
toolbar on the right.
+
Analysis
========
@@ -67,16 +70,16 @@ 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.
+via our ``development.ini`` using ``pyramid.includes``. We use this to load the
+configuration for the debugtoolbar.
You'll now see an attractive button on the right side of your browser, which
-you may click to provide introspective access to debugging information in a
-new browser tab. 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, there's 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.)
+you may click to provide introspective access to debugging information in a new
+rowser tab. 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,
+there's 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 injects a small amount of HTML/CSS into your app just
before the closing ``</body>`` tag in order to display itself. If you start to
@@ -86,13 +89,14 @@ temporarily.
.. seealso:: See also :ref:`pyramid_debugtoolbar <toolbar:overview>`.
-Extra Credit
+
+Extra credit
============
#. Why don't we add ``pyramid_debugtoolbar`` to the list of
``install_requires`` dependencies in ``debugtoolbar/setup.py``?
-#. Introduce a bug into your application: Change:
+#. Introduce a bug into your application. Change:
.. code-block:: python
@@ -106,7 +110,7 @@ Extra Credit
def hello_world(request):
return xResponse('<body><h1>Hello World!</h1></body>')
- Save, and visit http://localhost:6543/ again. Notice the nice
- traceback display. On the lowest line, click the "screen" icon to the
- right, and try typing the variable names ``request`` and ``Response``.
- What else can you discover?
+ Save, and visit http://localhost:6543/ again. Notice the nice traceback
+ display. On the lowest line, click the "screen" icon to the right, and try
+ typing the variable names ``request`` and ``Response``. What else can you
+ discover?
diff --git a/docs/quick_tutorial/forms.rst b/docs/quick_tutorial/forms.rst
index f81b88fc2..1f421ee67 100644
--- a/docs/quick_tutorial/forms.rst
+++ b/docs/quick_tutorial/forms.rst
@@ -1,35 +1,32 @@
.. _qtut_forms:
====================================
-18: Forms and Validation With Deform
+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.
+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 :ref:`Colander <colander:overview>`
+:ref:`Deform <deform:overview>` is one such library. In this step, we introduce
+Deform for our forms. This also gives us :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
+- Make a schema using Colander, the companion to Deform.
+
+- Create a form with Deform and change our views to handle validation.
-- Create a form with Deform and change our views to handle validation
Steps
=====
@@ -40,8 +37,8 @@ Steps
$ 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:
+#. 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:
@@ -50,23 +47,21 @@ Steps
.. code-block:: bash
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
-#. Register a static view in ``forms/tutorial/__init__.py`` for
- Deform's CSS/JS etc. as well as our demo wikipage scenario's
- views:
+#. Register a static view in ``forms/tutorial/__init__.py`` for Deform's CSS,
+ JavaScript, etc., as well as our demo wiki page'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``:
+#. 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``:
+#. A template for the top of the "wiki" in ``forms/tutorial/wiki_view.pt``:
.. literalinclude:: forms/tutorial/wiki_view.pt
:language: html
@@ -79,13 +74,26 @@ Steps
:language: html
:linenos:
-#. Finally, a template at ``forms/tutorial/wikipage_view.pt``
- for viewing a wiki page:
+#. Add a template at ``forms/tutorial/wikipage_view.pt`` for viewing a wiki
+ page:
.. literalinclude:: forms/tutorial/wikipage_view.pt
:language: html
:linenos:
+#. Our tests in ``forms/tutorial/tests.py`` don't run, so let's modify them:
+
+ .. literalinclude:: forms/tutorial/tests.py
+ :linenos:
+
+#. Run the tests:
+
+ .. code-block:: bash
+
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ..
+ 2 passed in 0.45 seconds
+
#. Run your Pyramid application with:
.. code-block:: bash
@@ -98,51 +106,50 @@ Steps
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 packages,
-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
+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 packages, 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 JavaScript just
+mentioned. Deform has a :term:`resource registry` which allows widgets to
+specify which JavaScript 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 URL whether you are walking up
+to it for 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 ``GET``, skip over and just return the form.
+
+- If you are doing a ``POST``, validate the form contents.
-- 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 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``.
-- 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.
-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
+Extra credit
============
-#. Give a try at a button that goes to a delete view for a
- particular wiki page.
+#. Give a try at a button that goes to a delete view for a particular wiki
+ page.
diff --git a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
index 547465018..d1fea0d7f 100644
--- a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
+++ b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt
@@ -4,12 +4,10 @@
<title>WikiPage: Add/Edit</title>
<tal:block tal:repeat="reqt view.reqts['css']">
<link rel="stylesheet" type="text/css"
- href="${request.static_url(reqt)}"/>
+ href="${request.static_url('deform:static/' + reqt)}"/>
</tal:block>
- <script src="${request.static_url('deform:static/scripts/jquery-2.0.3.min.js')}"
- type="text/javascript"></script>
<tal:block tal:repeat="reqt view.reqts['js']">
- <script src="${request.static_url(reqt)}"
+ <script src="${request.static_url('deform:static/' + reqt)}"
type="text/javascript"></script>
</tal:block>
</head>
diff --git a/docs/quick_tutorial/functional_testing.rst b/docs/quick_tutorial/functional_testing.rst
index 6f1544e79..33793578a 100644
--- a/docs/quick_tutorial/functional_testing.rst
+++ b/docs/quick_tutorial/functional_testing.rst
@@ -6,36 +6,39 @@
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.
+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 <http://docs.pylonsproject.org/projects/webtest/en/latest/>`_ 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.
-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
+- 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:
+#. First we copy the results of the previous step, as well as install the
+ ``webtest`` package:
.. code-block:: bash
$ cd ..; cp -r unit_testing functional_testing; cd functional_testing
- $ $VENV/bin/python setup.py develop
- $ $VENV/bin/easy_install webtest
+ $ $VENV/bin/pip install -e .
+ $ $VENV/bin/pip install webtest
#. Let's extend ``functional_testing/tutorial/tests.py`` to include a
functional test:
@@ -43,31 +46,28 @@ Steps
.. literalinclude:: functional_testing/tutorial/tests.py
:linenos:
- Be sure this file is not executable, or ``nosetests`` may not
- include your tests.
+ Be sure this file is not executable, or ``pytest`` may not include your
+ tests.
#. Now run the tests:
.. code-block:: bash
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ..
+ 2 passed in 0.25 seconds
- $ $VENV/bin/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.
+We now have the end-to-end testing we were looking for. WebTest lets us simply
+extend our existing ``pytest``-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
+Extra credit
============
#. Why do our functional tests use ``b''``?
diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst
index 4ae80ca87..56dccde58 100644
--- a/docs/quick_tutorial/hello_world.rst
+++ b/docs/quick_tutorial/hello_world.rst
@@ -4,40 +4,40 @@
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.
+What's the simplest way to get started in Pyramid? A single-file module. No
+Python packages, no ``pip install -e .``, 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*.
+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.
-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.
-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.
-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
+- Get a running Pyramid web application, as simply as possible.
+
+- Use that as a well-understood base for adding each unit of complexity.
-- Use that as a well-understood base for adding each unit of complexity
+- Initial exposure to WSGI apps, requests, views, and responses.
-- Initial exposure to WSGI apps, requests, views, and responses
Steps
=====
@@ -64,32 +64,31 @@ Steps
#. Open http://localhost:6543/ in your browser.
+
Analysis
========
-New to Python web programming? If so, some lines in module merit
+New to Python web programming? If so, some lines in the module merit
explanation:
-#. *Line 11*. The ``if __name__ == '__main__':`` is Python's way of
- saying "Start here when running from the command line", rather than
- when this module is imported.
+#. *Line 11*. The ``if __name__ == '__main__':`` is Python's way of saying,
+ "Start here when running from the command line", rather than when this
+ module is imported.
+
+#. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect :term:`view`
+ code to a particular URL :term:`route`.
-#. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect
- :term:`view` code to a particular URL :term:`route`.
+#. *Lines 6-8*. Implement the view code that generates the :term:`response`.
-#. *Lines 6-8*. Implement the view code that generates the
- :term:`response`.
+#. *Lines 15-17*. Publish a :term:`WSGI` app using an HTTP server.
-#. *Lines 15-17*. Publish a :term:`WSGI` app using an HTTP
- server.
+As shown in this example, the :term:`configurator` plays a central role in
+Pyramid development. Building an application from loosely-coupled parts via
+:ref:`configuration_narr` is a central idea in Pyramid, one that we will
+revisit regularly in this *Quick Tutorial*.
-As shown in this example, the :term:`configurator` plays a
-central role in Pyramid development. Building an application from
-loosely-coupled parts via :ref:`configuration_narr` is a
-central idea in Pyramid, one that we will revisit regularly in this
-*Quick Tour*.
-Extra Credit
+Extra credit
============
#. Why do we do this:
@@ -106,9 +105,9 @@ Extra Credit
#. 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?
+#. Put something invalid, such as ``print xyz``, in the view function. Kill
+ your ``python app.py`` with ``ctrl-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?
+#. The ``GI`` in ``WSGI`` stands for "Gateway Interface". What web standard is
+ this modelled after?
diff --git a/docs/quick_tutorial/index.rst b/docs/quick_tutorial/index.rst
index 9373fe38a..29b4d8fb7 100644
--- a/docs/quick_tutorial/index.rst
+++ b/docs/quick_tutorial/index.rst
@@ -4,12 +4,12 @@
Quick Tutorial for Pyramid
==========================
-Pyramid is a web framework for Python 2 and 3. This tutorial gives a
-Python 3/2-compatible, high-level tour of the major features.
+Pyramid is a web framework for Python 2 and 3. This tutorial gives a Python
+3/2-compatible, high-level tour of the major features.
-This 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.
+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
========
diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst
index 36942c767..9a65d66d1 100644
--- a/docs/quick_tutorial/ini.rst
+++ b/docs/quick_tutorial/ini.rst
@@ -7,28 +7,30 @@
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 <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 installing and providing
-"entry points" for Python projects. Pyramid uses an entry point to
-let a Pyramid application know where to find the WSGI app.
+Pyramid has a first-class concept of :ref:`configuration <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 installing and providing "entry
+points" for Python projects. Pyramid uses an entry point to let a Pyramid
+application know where to find the WSGI app.
+
Objectives
==========
-- Modify our ``setup.py`` to have an entry point telling Pyramid the
- location of the WSGI app
+- Modify our ``setup.py`` to have an entry point telling Pyramid the location
+ of the WSGI app.
+
+- Create an application driven by an ``.ini`` file.
-- Create an application driven by a ``.ini`` file
+- Start the application with Pyramid's ``pserve`` command.
-- Startup the application with Pyramid's ``pserve`` command
+- Move code into the package's ``__init__.py``.
-- Move code into the package's ``__init__.py``
Steps
=====
@@ -39,18 +41,18 @@ Steps
$ cd ..; cp -r package ini; cd ini
-#. Our ``ini/setup.py`` needs a setuptools "entry point" in the
- ``setup()`` function:
+#. 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``:
+#. We can now install our project, thus generating (or re-generating) an "egg"
+ at ``ini/tutorial.egg-info``:
.. code-block:: bash
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. Let's make a file ``ini/development.ini`` for our configuration:
@@ -58,8 +60,8 @@ Steps
:language: ini
:linenos:
-#. We can refactor our startup code from the previous step's ``app.py``
- into ``ini/tutorial/__init__.py``:
+#. We can refactor our startup code from the previous step's ``app.py`` into
+ ``ini/tutorial/__init__.py``:
.. literalinclude:: ini/tutorial/__init__.py
:linenos:
@@ -81,27 +83,26 @@ Steps
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
+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 <startup_chapter>`:
-- ``pserve`` looks for ``[app:main]`` and finds ``use = egg:tutorial``
+- ``pserve`` looks for ``[app:main]`` and finds ``use = egg:tutorial``.
-- The projects's ``setup.py`` has defined an "entry point" (lines 9-12)
- for the project "main" entry point of ``tutorial:main``
+- The projects's ``setup.py`` has defined an "entry point" (lines 9-12) for the
+ project's "main" entry point of ``tutorial:main``.
-- The ``tutorial`` package's ``__init__`` has a ``main`` function
+- The ``tutorial`` package's ``__init__`` has a ``main`` function.
-- This function is invoked, with the values from certain ``.ini``
- sections passed in
+- This function is invoked, with the values from certain ``.ini`` sections
+ passed in.
The ``.ini`` file is also used for two other functions:
-- *Configuring the WSGI server*. ``[server:main]`` wires up the choice of
- which WSGI *server* for your WSGI *application*. In this case, we are using
- ``wsgiref`` bundled in the Python library. It also wires up the *port
- number*: ``port = 6543`` tells ``wsgiref`` to listen on port 6543.
+- *Configuring the WSGI server*. ``[server:main]`` wires up the choice of which
+ WSGI *server* for your WSGI *application*. In this case, we are using
+ ``wsgiref`` bundled in the Python library. It also wires up the *port
+ number*: ``port = 6543`` tells ``wsgiref`` to listen on port 6543.
- *Configuring Python logging*. Pyramid uses Python standard logging, which
needs a number of configuration values. The ``.ini`` serves this function.
@@ -109,27 +110,27 @@ The ``.ini`` file is also used for two other functions:
request.
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``.
+``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.
+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
+
+Extra credit
============
-#. If you don't like configuration and/or ``.ini`` files,
- could you do this yourself in Python code?
+#. 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?
+#. 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 declared ``tutorial:main`` function. Why not?
+#. The entry point in ``setup.py`` didn't mention ``__init__.py`` when it
+ declared ``tutorial:main`` function. Why not?
#. What is the purpose of ``**settings``? What does the ``**`` signify?
@@ -139,4 +140,3 @@ Extra Credit
:ref:`what_is_this_pserve_thing`,
:ref:`environment_chapter`,
:ref:`paste_chapter`
-
diff --git a/docs/quick_tutorial/jinja2.rst b/docs/quick_tutorial/jinja2.rst
index 2121803f9..2fc68827b 100644
--- a/docs/quick_tutorial/jinja2.rst
+++ b/docs/quick_tutorial/jinja2.rst
@@ -4,33 +4,34 @@
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 modeled 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.
+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 modeled
+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
+- Show Pyramid's support for different templating systems.
+
+- Learn about installing Pyramid add-ons.
-- Learn about installing Pyramid add-ons
Steps
=====
-#. In this step let's start by copying the ``view_class`` step's
- directory, and then installing the ``pyramid_jinja2`` add-on.
+#. In this step let's start by copying the ``view_class`` step's directory,
+ and then installing the ``pyramid_jinja2`` add-on.
.. code-block:: bash
$ cd ..; cp -r view_classes jinja2; cd jinja2
- $ $VENV/bin/python setup.py develop
- $ $VENV/bin/easy_install pyramid_jinja2
+ $ $VENV/bin/pip install -e .
+ $ $VENV/bin/pip install pyramid_jinja2
-#. We need to include ``pyramid_jinja2`` in
- ``jinja2/tutorial/__init__.py``:
+#. We need to include ``pyramid_jinja2`` in ``jinja2/tutorial/__init__.py``:
.. literalinclude:: jinja2/tutorial/__init__.py
:linenos:
@@ -49,7 +50,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ....
+ 4 passed in 0.40 seconds
#. Run your Pyramid application with:
@@ -59,30 +62,30 @@ Steps
#. 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
+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
+virtual environment. 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 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.
+
-Extra Credit
+Extra credit
============
-#. Our project now depends on ``pyramid_jinja2``. We installed that
- dependency manually. What is another way we could have made the
- association?
+#. Our project now depends on ``pyramid_jinja2``. We installed that dependency
+ manually. What is another way we could have made the association?
#. We used ``config.include`` which is an imperative configuration to get the
- :term:`Configurator` to load ``pyramid_jinja2``'s configuration.
- What is another way could include it into the config?
+ :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
+.. seealso:: `Jinja2 homepage <http://jinja.pocoo.org/>`_, and
:ref:`pyramid_jinja2 Overview <jinja2:overview>`
diff --git a/docs/quick_tutorial/json.rst b/docs/quick_tutorial/json.rst
index aa789d833..ff153d2b5 100644
--- a/docs/quick_tutorial/json.rst
+++ b/docs/quick_tutorial/json.rst
@@ -1,27 +1,28 @@
.. _qtut_json:
========================================
-14: Ajax Development With JSON Renderers
+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*.
+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.
+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.
-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
=====
@@ -31,22 +32,20 @@ Steps
.. code-block:: bash
$ cd ..; cp -r view_classes json; cd json
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
-#. We add a new route for ``hello_json`` in
- ``json/tutorial/__init__.py``:
+#. 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 in ``views.py``:
+#. Rather than implement a new view, we will "stack" another decorator on the
+ ``hello`` view in ``views.py``:
.. literalinclude:: json/tutorial/views.py
:linenos:
-#. We need a new functional test at the end of
- ``json/tutorial/tests.py``:
+#. We need a new functional test at the end of ``json/tutorial/tests.py``:
.. literalinclude:: json/tutorial/tests.py
:linenos:
@@ -55,7 +54,10 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ .....
+ 5 passed in 0.47 seconds
+
#. Run your Pyramid application with:
@@ -63,40 +65,39 @@ Steps
$ $VENV/bin/pserve development.ini --reload
-#. Open http://localhost:6543/howdy.json in your browser and you
- will see the resulting JSON response.
+#. 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.
+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:
+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
+- Adding a route to map ``/howdy.json`` to a route name.
-- Providing a ``@view_config`` that associated that route name with an
- existing view
+- 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
+- *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.
+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 implementations.
-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.
+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:`views_which_use_a_renderer`,
:ref:`json_renderer`, and
diff --git a/docs/quick_tutorial/logging.rst b/docs/quick_tutorial/logging.rst
index 5d29cd196..cbbf7860e 100644
--- a/docs/quick_tutorial/logging.rst
+++ b/docs/quick_tutorial/logging.rst
@@ -4,28 +4,30 @@
16: Collecting Application Info With Logging
============================================
-Capture debugging and error output from your web applications using
-standard Python 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*.
+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`` has 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.
-Fortunately Pyramid uses the normal Python approach to logging. The
-scaffold generated in your ``development.ini`` has 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
+- Inspect the configuration setup used for logging.
+
+- Add logging statements to your view code.
-- Add logging statements to your view code
Steps
=====
@@ -35,15 +37,15 @@ Steps
.. code-block:: bash
$ cd ..; cp -r view_classes logging; cd logging
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. Extend ``logging/tutorial/views.py`` to log a message:
.. literalinclude:: logging/tutorial/views.py
:linenos:
-#. Finally let's edit ``development.ini`` configuration file
- to enable logging for our Pyramid application:
+#. Finally let's edit ``development.ini`` configuration file to enable logging
+ for our Pyramid application:
.. literalinclude:: logging/development.ini
:language: ini
@@ -52,7 +54,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ....
+ 4 passed in 0.41 seconds
#. Run your Pyramid application with:
@@ -60,19 +64,21 @@ Steps
$ $VENV/bin/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.
+#. 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
========
-In our configuration file ``development.ini``, 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::
+In our configuration file ``development.ini``, our ``tutorial`` Python package
+is set up 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:
+
+.. code-block:: text
- 2013-08-09 10:42:42,968 DEBUG [tutorial.views][MainThread] In home view
+ 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.
diff --git a/docs/quick_tutorial/more_view_classes.rst b/docs/quick_tutorial/more_view_classes.rst
index afbb7cc3a..30234ea2e 100644
--- a/docs/quick_tutorial/more_view_classes.rst
+++ b/docs/quick_tutorial/more_view_classes.rst
@@ -6,48 +6,49 @@
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.
+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 a ``__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.
-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.
+At first, our views were 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
+<class_as_view>` makes sense:
-At first, our views were 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 <class_as_view>` makes sense:
+- Group views.
-- Group views
+- Centralize some repetitive defaults.
-- Centralize some repetitive defaults
+- Share some state and helpers.
-- Share some state and helpers
+Pyramid views have :ref:`view predicates <view_configuration_parameters>` that
+determine which view is matched to a request, based on factors such as the
+request method, the form parameters, and so on. These predicates provide many
+axes of flexibility.
-Pyramid views have :ref:`view predicates <view_configuration_parameters>`
-that determine which view is matched to a request, based on factors
-such as the request method, the form parameters, etc. These predicates
-provide many axes of flexibility.
+The following shows a simple example with four operations: view a home page
+which leads to a form, save a change, and press the delete button.
-The following shows a simple example with four 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
+- Group related views into a view class.
-- Centralize configuration with class-level ``@view_defaults``
+- Centralize configuration with class-level ``@view_defaults``.
-- Dispatch one route/URL to multiple views based on request data
+- Dispatch one route/URL to multiple views based on request data.
+
+- Share states and logic between views and templates via the view class.
-- Share states and logic between views and templates via the view class
Steps
=====
@@ -57,7 +58,7 @@ Steps
.. code-block:: bash
$ cd ..; cp -r templating more_view_classes; cd more_view_classes
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. Our route in ``more_view_classes/tutorial/__init__.py`` needs some
replacement patterns:
@@ -71,8 +72,7 @@ Steps
.. literalinclude:: more_view_classes/tutorial/views.py
:linenos:
-#. Our primary view needs a template at
- ``more_view_classes/tutorial/home.pt``:
+#. Our primary view needs a template at ``more_view_classes/tutorial/home.pt``:
.. literalinclude:: more_view_classes/tutorial/home.pt
:language: html
@@ -105,12 +105,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
- .
- ----------------------------------------------------------------------
- Ran 2 tests in 0.248s
-
- OK
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ..
+ 2 passed in 0.40 seconds
#. Run your Pyramid application with:
@@ -118,29 +115,27 @@ Steps
$ $VENV/bin/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.
+#. 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:
+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.
+- 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
+- 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 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"/>``.
+- 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, how to
decide which view to use:
@@ -149,49 +144,53 @@ decide which view to use:
- 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:
+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}``.
+These are then available both in the view methods and 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 hardcoded the URLs, such as:
-As a note, we made a switch in our templates on how we generate URLs.
-We previously hardcode the URLs, such as::
+.. code-block:: html
<a href="/howdy/jane/doe">Howdy</a>
-In ``home.pt`` we switched to::
+In ``home.pt`` we switched to:
+
+.. code-block:: xml
<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.
+Pyramid has rich facilities to help generate URLs in a flexible, non-error
+prone fashion.
-Extra Credit
+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
- ``POST`` used by ``delete``?
+#. The ``edit`` and ``delete`` views are both receive ``POST`` requests. Why
+ does the ``edit`` view configuration not catch 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?
+#. 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
+#. There is also a ``request.route_path`` API. How does this differ from
``request.route_url``?
.. seealso:: :ref:`class_as_view`, `Weird Stuff You Can Do With
diff --git a/docs/quick_tutorial/package.rst b/docs/quick_tutorial/package.rst
index 54a6a0bd9..94cb39fc9 100644
--- a/docs/quick_tutorial/package.rst
+++ b/docs/quick_tutorial/package.rst
@@ -3,50 +3,48 @@
============================================
Most modern Python development is done using Python packages, an approach
-Pyramid puts to good use. In this step we redo "Hello World" as a
-minimum Python package inside a minimum Python project.
+Pyramid puts to good use. In this step we redo "Hello World" as a minimal
+Python package inside a minimal 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.
+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:
+Packages can be bundled up, made available for installation, and installed
+through a toolchain oriented around a ``setup.py`` file. For this tutorial,
+this is all you need to know:
-- We will have a directory for each tutorial step as a setuptools *project*
+- We will have a directory for each tutorial step as a *project*.
-- This project will contain a ``setup.py`` which injects the features
- of the setuptool's project machinery into the directory
+- This project will contain a ``setup.py`` which injects the features of the
+ 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
+ *package* using an ``__init__.py`` Python module file.
-- We will run ``python setup.py develop`` to install our project in
- development mode
+- We will run ``pip install -e .`` to install our project in development mode.
In summary:
-- You'll do your development in a Python *package*
+- You'll do your development in a Python *package*.
+
+- That package will be part of a *project*.
-- That package will be part of a setuptools *project*
Objectives
==========
-- Make a Python "package" directory with an ``__init__.py``
+- Make a Python "package" directory with an ``__init__.py``.
-- Get a minimum Python "project" in place by making a ``setup.py``
+- Get a minimum Python "project" in place by making a ``setup.py``.
+
+- Install our ``tutorial`` project in development mode.
-- Install our ``tutorial`` project in development mode
Steps
=====
@@ -61,12 +59,12 @@ Steps
.. literalinclude:: package/setup.py
-#. Make the new project installed for development then make a directory
- for the actual code:
+#. Make the new project installed for development then make a directory for the
+ actual code:
.. code-block:: bash
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
$ mkdir tutorial
#. Enter the following into ``package/tutorial/__init__.py``:
@@ -85,27 +83,29 @@ Steps
#. 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.)
+Python packages give us an organized unit of project development. Python
+projects, via ``setup.py``, give us special features when our package is
+installed (in this case, in local development mode, also called local editable
+mode as indicated by ``-e .``).
-In this step we have a Python package called ``tutorial``. We use the
-same name in each step of the tutorial, to avoid unnecessary retyping.
+In this step we have a Python package called ``tutorial``. We use the same name
+in each step of the tutorial, to avoid unnecessary retyping.
-Above this ``tutorial`` directory we have the files that handle the
-packaging of this project. At the moment, all we need is a
-bare-bones ``setup.py``.
+Above this ``tutorial`` directory we have the files that handle the packaging
+of this project. At the moment, all we need is a bare-bones ``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.
+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
+tries to capture how this stuff works one 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>`_
+.. seealso:: :ref:`Python Packages <python:tut-packages>` and `Working in
+ "Development Mode"
+ <https://packaging.python.org/en/latest/distributing/#working-in-development-mode>`_.
diff --git a/docs/quick_tutorial/request_response.rst b/docs/quick_tutorial/request_response.rst
index 4f8de0221..0ac9b4f6d 100644
--- a/docs/quick_tutorial/request_response.rst
+++ b/docs/quick_tutorial/request_response.rst
@@ -5,33 +5,32 @@
=======================================
Web applications handle incoming requests and return outgoing responses.
-Pyramid makes working with requests and responses convenient and
-reliable.
+Pyramid makes working with requests and responses convenient and reliable.
+
Objectives
==========
-- Learn the background on Pyramid's choices for requests and responses
+- Learn the background on Pyramid's choices for requests and responses.
+
+- Grab data out of the request.
-- Grab data out of the request
+- Change information in the response headers.
-- 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.
+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, and
+so on). 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>`.
-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
=====
@@ -41,7 +40,7 @@ Steps
.. code-block:: bash
$ cd ..; cp -r view_classes request_response; cd request_response
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. Simplify the routes in ``request_response/tutorial/__init__.py``:
@@ -62,7 +61,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ .....
+ 5 passed in 0.30 seconds
#. Run your Pyramid application with:
@@ -70,37 +71,39 @@ Steps
$ $VENV/bin/pserve development.ini --reload
-#. Open http://localhost:6543/ in your browser. You will be
- redirected to http://localhost:6543/plain
+#. 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 <http_redirect>` by returning a
-special object from a view or raising a special exception.
+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
+<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:
-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::
+.. code-block:: text
URL http://localhost:6543/plain?name=alice with name: alice
-Finally, we set the response's content type and body, then return the
-Response.
+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``.
-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
+Extra credit
============
-#. Could we also ``raise HTTPFound(location='/plain')`` instead of
- returning it? If so, what's the difference?
+#. Could we also ``raise HTTPFound(location='/plain')`` instead of returning
+ it? If so, what's the difference?
.. seealso:: :ref:`webob_chapter`,
:ref:`generate redirects <http_redirect>`
diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst
index a737ede0e..958347a90 100644
--- a/docs/quick_tutorial/requirements.rst
+++ b/docs/quick_tutorial/requirements.rst
@@ -4,75 +4,67 @@
Requirements
============
-Let's get our tutorial environment setup. Most of the setup work is in
-standard Python development practices (install Python,
-make an isolated environment, and setup packaging tools.)
+Let's get our tutorial environment set up. Most of the set up work is in
+standard Python development practices (install Python and make an isolated
+virtual environment.)
.. note::
- Pyramid encourages standard Python development practices with
- packaging tools, virtual environments, logging, and so on. There
- are many variations, implementations, and opinions across the Python
- community. For consistency, ease of documentation maintenance,
- and to minimize confusion, the Pyramid *documentation* has adopted
- specific conventions.
+ Pyramid encourages standard Python development practices with packaging
+ tools, virtual environments, logging, and so on. There are many variations,
+ implementations, and opinions across the Python community. For consistency,
+ ease of documentation maintenance, and to minimize confusion, the Pyramid
+ *documentation* has adopted specific conventions that are consistent with the
+ :term:`Python Packaging Authority`.
This *Quick Tutorial* is based on:
-* **Python 3.3**. Pyramid fully supports Python 3.2+ and Python 2.6+.
- This tutorial uses **Python 3.3** but runs fine under Python 2.7.
+* **Python 3.5**. Pyramid fully supports Python 3.3+ and Python 2.7+. This
+ tutorial uses **Python 3.5** but runs fine under Python 2.7.
-* **pyvenv**. We believe in virtual environments. For this tutorial,
- we use Python 3.3's built-in solution, the ``pyvenv`` command.
- For Python 2.7, you can install ``virtualenv``.
+* **venv**. We believe in virtual environments. For this tutorial, we use
+ Python 3.5's built-in solution :term:`venv`. For Python 2.7, you can install
+ :term:`virtualenv`.
-* **setuptools and easy_install**. We use
- `setuptools <https://pypi.python.org/pypi/setuptools/>`_
- and its ``easy_install`` for package management.
+* **pip**. We use :term:`pip` for package management.
-* **Workspaces, projects, and packages.** Our home directory
- will contain a *tutorial workspace* with our Python virtual
- environment(s) and *Python projects* (a directory with packaging
- information and *Python packages* of working code.)
+* **Workspaces, projects, and packages.** Our home directory will contain a
+ *tutorial workspace* with our Python virtual environment and *Python
+ projects* (a directory with packaging information and *Python packages* of
+ working code.)
-* **Unix commands**. Commands in this tutorial use UNIX syntax and
- paths. Windows users should adjust commands accordingly.
+* **Unix commands**. Commands in this tutorial use UNIX syntax and paths.
+ Windows users should adjust commands accordingly.
.. note::
-
Pyramid was one of the first web frameworks to fully support Python 3 in
October 2011.
+.. note::
+ Windows commands use the plain old MSDOS shell. For PowerShell command
+ syntax, see its documentation.
+
Steps
=====
-#. :ref:`install-python-3.3-or-greater`
+#. :ref:`install-python-3`
#. :ref:`create-a-project-directory-structure`
#. :ref:`set-an-environment-variable`
#. :ref:`create-a-virtual-environment`
-#. :ref:`install-setuptools-(python-packaging-tools)`
#. :ref:`install-pyramid`
-.. _install-python-3.3-or-greater:
-Install Python 3.3 or greater
------------------------------
+.. _install-python-3:
-Download the latest standard Python 3.3+ release (not development release)
-from `python.org <https://www.python.org/downloads/>`_.
+Install Python 3
+----------------
-Windows and Mac OS X users can download and run an installer.
+See the detailed recommendation for your operating system described under
+:ref:`installing_chapter`.
-Windows users should also install the `Python for Windows extensions
-<http://sourceforge.net/projects/pywin32/files/pywin32/>`_. Carefully read the
-``README.txt`` file at the end of the list of builds, and follow its
-directions. Make sure you get the proper 32- or 64-bit build and Python
-version.
-
-Linux users can either use their package manager to install Python 3.3
-or may `build Python 3.3 from source
-<http://pyramid.readthedocs.org/en/master/narr/install.html#package-manager-
-method>`_.
+- :ref:`for-mac-os-x-users`
+- :ref:`if-you-don-t-yet-have-a-python-interpreter-unix`
+- :ref:`if-you-don-t-yet-have-a-python-interpreter-windows`
.. _create-a-project-directory-structure:
@@ -80,23 +72,22 @@ method>`_.
Create a project directory structure
------------------------------------
-We will arrive at a directory structure of
-``workspace->project->package``, with our workspace named
-``quick_tutorial``. The following tree diagram shows how this will be
-structured and where our virtual environment will reside as we proceed through
-the tutorial:
+We will arrive at a directory structure of ``workspace -> project -> package``,
+where our workspace is named ``quick_tutorial``. The following tree diagram
+shows how this will be structured, and where our :term:`virtual environment`
+will reside as we proceed through the tutorial:
.. code-block:: text
- └── ~
- └── projects
- └── quick_tutorial
- ├── env
- └── step_one
- ├── intro
- │ ├── __init__.py
- │ └── app.py
- └── setup.py
+ `── ~
+ `── projects
+ `── quick_tutorial
+ │── env
+ `── step_one
+ │── intro
+ │ │── __init__.py
+ │ `── app.py
+ `── setup.py
For Linux, the commands to do so are as follows:
@@ -109,105 +100,84 @@ For Linux, the commands to do so are as follows:
For Windows:
-.. code-block:: posh
+.. code-block:: doscon
# Windows
c:\> cd \
c:\> mkdir projects\quick_tutorial
c:\> cd projects\quick_tutorial
-In the above figure, your user home directory is represented by ``~``. In
-your home directory, all of your projects are in the ``projects`` directory.
-This is a general convention not specific to Pyramid that many developers use.
-Windows users will do well to use ``c:\`` as the location for ``projects`` in
-order to avoid spaces in any of the path names.
+In the above figure, your user home directory is represented by ``~``. In your
+home directory, all of your projects are in the ``projects`` directory. This is
+a general convention not specific to Pyramid that many developers use. Windows
+users will do well to use ``c:\`` as the location for ``projects`` in order to
+avoid spaces in any of the path names.
Next within ``projects`` is your workspace directory, here named
``quick_tutorial``. A workspace is a common term used by integrated
-development environments (IDE) like PyCharm and PyDev that stores
-isolated Python environments (virtualenvs) and specific project files
-and repositories.
+development environments (IDE), like PyCharm and PyDev, where virtual
+environments, specific project files, and repositories are stored.
.. _set-an-environment-variable:
-Set an Environment Variable
+Set an environment variable
---------------------------
-This tutorial will refer frequently to the location of the virtual
-environment. We set an environment variable to save typing later.
+This tutorial will refer frequently to the location of the :term:`virtual
+environment`. We set an environment variable to save typing later.
.. code-block:: bash
# Mac and Linux
$ export VENV=~/projects/quick_tutorial/env
+.. code-block:: doscon
+
# Windows
- # TODO: This command does not work
c:\> set VENV=c:\projects\quick_tutorial\env
.. _create-a-virtual-environment:
-Create a Virtual Environment
+Create a virtual environment
----------------------------
-.. warning:: The current state of isolated Python environments using
- ``pyvenv`` on Windows is suboptimal in comparison to Mac and Linux. See
- http://stackoverflow.com/q/15981111/95735 for a discussion of the issue
- and `PEP 453 <http://www.python.org/dev/peps/pep-0453/>`_ for a proposed
- resolution.
-
-``pyvenv`` is a tool to create isolated Python 3.3 environments, each
-with its own Python binary and independent set of installed Python
-packages in its site directories. Let's create one, using the location
-we just specified in the environment variable.
+``venv`` is a tool to create isolated Python 3 environments, each with its own
+Python binary and independent set of installed Python packages in its site
+directories. Let's create one, using the location we just specified in the
+environment variable.
.. code-block:: bash
# Mac and Linux
- $ pyvenv $VENV
+ $ python3 -m venv $VENV
- # Windows
- c:\> c:\Python33\python -m venv %VENV%
+.. code-block:: doscon
-.. seealso:: See also Python 3's :mod:`venv module <python3:venv>`,
- Python 2's `virtualenv <http://www.virtualenv.org/en/latest/>`_
- package,
- :ref:`Installing Pyramid on a Windows System <installing_windows>`
+ # Windows
+ c:\> c:\Python35\python -m venv %VENV%
+.. seealso:: See also Python 3's :mod:`venv module <python:venv>` and Python
+ 2's `virtualenv <https://virtualenv.pypa.io/en/latest/>`_ package.
-.. _install-setuptools-(python-packaging-tools):
-Install ``setuptools`` (Python packaging tools)
------------------------------------------------
+Update packaging tools in the virtual environment
+-------------------------------------------------
-The following command will download a script to install ``setuptools``, then
-pipe it to your environment's version of Python.
+It's always a good idea to update to the very latest version of packaging tools
+because the installed Python bundles only the version that was available at the
+time of its release.
.. code-block:: bash
# Mac and Linux
- $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $VENV/bin/python
-
- # Windows
- #
- # Use your web browser to download this file:
- # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py
- #
- # ...and save it to:
- # c:\projects\quick_tutorial\ez_setup.py
- #
- # Then run the following command:
-
- c:\> %VENV%\Scripts\python ez_setup.py
+ $VENV/bin/pip install --upgrade pip setuptools
-If ``wget`` complains with a certificate error, then run this command instead:
+.. code-block:: doscon
-.. code-block:: bash
-
- # Mac and Linux
- $ wget --no-check-certificate https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $VENV/bin/python
+ # Windows
+ c:\> %VENV%\Scripts\pip install --upgrade pip setuptools
.. _install-pyramid:
@@ -216,48 +186,29 @@ Install Pyramid
---------------
We have our Python standard prerequisites out of the way. The Pyramid
-part is pretty easy:
+part is pretty easy.
.. parsed-literal::
# Mac and Linux
- $ $VENV/bin/easy_install "pyramid==\ |release|\ "
+ $ $VENV/bin/pip install "pyramid==\ |release|\ "
# Windows
- c:\\> %VENV%\\Scripts\\easy_install "pyramid==\ |release|\ "
+ c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
Our Python virtual environment now has the Pyramid software available.
-You can optionally install some of the extra Python packages used
-during this tutorial:
+You can optionally install some of the extra Python packages used in this
+tutorial.
.. code-block:: bash
# Mac and Linux
- $ $VENV/bin/easy_install nose webtest deform sqlalchemy \
- pyramid_chameleon pyramid_debugtoolbar waitress \
- pyramid_tm zope.sqlalchemy
-
- # Windows
- c:\> %VENV%\Scripts\easy_install nose webtest deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar waitress pyramid_tm zope.sqlalchemy
+ $ $VENV/bin/pip install webtest pytest pytest-cov deform sqlalchemy \
+ pyramid_chameleon pyramid_debugtoolbar pyramid_jinja2 waitress \
+ pyramid_tm zope.sqlalchemy
+.. code-block:: doscon
-.. note::
-
- Why ``easy_install`` and not ``pip``? Pyramid encourages use of namespace
- packages, for which ``pip``'s support is less-than-optimal. Also, Pyramid's
- dependencies use some optional C extensions for performance: with
- ``easy_install``, Windows users can get these extensions without needing
- a C compiler (``pip`` does not support installing binary Windows
- distributions, except for ``wheels``, which are not yet available for
- all dependencies).
-
-.. seealso:: See also :ref:`installing_unix`. For instructions to set up your
- Python environment for development using Windows or Python 2, see Pyramid's
- :ref:`Before You Install <installing_chapter>`.
-
- See also Python 3's :mod:`venv module <python3:venv>`, the `setuptools
- installation instructions
- <https://pypi.python.org/pypi/setuptools/0.9.8#installation-instructions>`_,
- and `easy_install help <https://pypi.python.org/pypi/setuptools/0.9.8#using-setuptools-and-easyinstall>`_.
-
+ # Windows
+ c:\> %VENV%\Scripts\pip install webtest deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar pyramid_jinja2 waitress pyramid_tm zope.sqlalchemy
diff --git a/docs/quick_tutorial/routing.rst b/docs/quick_tutorial/routing.rst
index 1b79a5889..27c8c2c22 100644
--- a/docs/quick_tutorial/routing.rst
+++ b/docs/quick_tutorial/routing.rst
@@ -4,41 +4,44 @@
11: Dispatching URLs To Views With Routing
==========================================
-Routing matches incoming URL patterns to view code. Pyramid's routing
-has a number of useful features.
+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.
+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
+- 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
+- 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
+ 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://static.repoze.org/casts/videotags.html>`_ if you're interested in
doing so.
+
Objectives
==========
-- Define a route that extracts part of the URL into a Python dictionary
+- Define a route that extracts part of the URL into a Python dictionary.
+
+- Use that dictionary data in a view.
-- Use that dictionary data in a view
Steps
=====
@@ -48,7 +51,7 @@ Steps
.. code-block:: bash
$ cd ..; cp -r view_classes routing; cd routing
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. Our ``routing/tutorial/__init__.py`` needs a route with a replacement
pattern:
@@ -76,7 +79,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
+ $ $VENV/bin/$VENV/bin/py.test tutorial/tests.py -q
+ ..
+ 2 passed in 0.39 seconds
#. Run your Pyramid application with:
@@ -86,6 +91,7 @@ Steps
#. Open http://localhost:6543/howdy/amy/smith in your browser.
+
Analysis
========
@@ -95,27 +101,24 @@ In ``__init__.py`` we see an important change in our route declaration:
config.add_route('hello', '/howdy/{first}/{last}')
-With this we tell the :term:`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:
+With this we tell the :term:`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.
+``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
+Extra credit
============
-#. What happens if you to go the URL
- http://localhost:6543/howdy? Is this the result that you
- expected?
+#. 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>`_
+.. seealso:: `Weird Stuff You Can Do With URL Dispatch
+ <http://www.plope.com/weird_pyramid_urldispatch>`_
diff --git a/docs/quick_tutorial/scaffolds.rst b/docs/quick_tutorial/scaffolds.rst
index 4f2694100..7845f2b71 100644
--- a/docs/quick_tutorial/scaffolds.rst
+++ b/docs/quick_tutorial/scaffolds.rst
@@ -4,29 +4,30 @@
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.
+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.
+We're going to cover a lot in this tutorial, focusing on one topic at a time
+and writing everything from scratch. As a warm up, 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.
-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
+- 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.
-- Start up a Pyramid application and visit it in a web browser
Steps
=====
@@ -47,21 +48,22 @@ Steps
$ $VENV/bin/pcreate --scaffold starter scaffolds
-#. Use normal Python development to setup our project for development:
+#. Install our project in editable mode for development in the current
+ directory:
.. code-block:: bash
$ cd scaffolds
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
-#. Startup the application by pointing Pyramid's ``pserve`` command at
- the project's (generated) configuration file:
+#. Start up the application by pointing Pyramid's ``pserve`` command at the
+ project's (generated) configuration file:
.. code-block:: bash
$ $VENV/bin/pserve development.ini --reload
- On startup, ``pserve`` logs some output:
+ On start up, ``pserve`` logs some output:
.. code-block:: bash
@@ -74,13 +76,12 @@ Steps
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.
+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``.)
+``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``).
diff --git a/docs/quick_tutorial/sessions.rst b/docs/quick_tutorial/sessions.rst
index f97405500..df4887a4b 100644
--- a/docs/quick_tutorial/sessions.rst
+++ b/docs/quick_tutorial/sessions.rst
@@ -6,25 +6,28 @@
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`.
+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. Third party packages such as
-``pyramid_redis_sessions`` provide richer session support. Or you can create
-your own custom sessioning engine. Let's take a look at the
-:doc:`built-in sessioning support <../narr/sessions>`.
+`pyramid_redis_sessions
+<https://github.com/ericrasmussen/pyramid_redis_sessions>`_ provide richer
+session support. Or you can create your own custom sessioning engine. Let's
+take a look at the :doc:`built-in sessioning support <../narr/sessions>`.
+
Objectives
==========
-- Make a session factory using a built-in, simple Pyramid sessioning
- system
+- Make a session factory using a built-in, simple Pyramid sessioning system.
+
+- Change our code to use a session.
-- Change our code to use a session
Steps
=====
@@ -34,16 +37,15 @@ Steps
.. code-block:: bash
$ cd ..; cp -r view_classes sessions; cd sessions
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
-#. Our ``sessions/tutorial/__init__.py`` needs a choice of session
- factory to get registered with the :term:`configurator`:
+#. 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``:
+#. Our views in ``sessions/tutorial/views.py`` can now use ``request.session``:
.. literalinclude:: sessions/tutorial/views.py
:linenos:
@@ -58,7 +60,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ....
+ 4 passed in 0.42 seconds
#. Run your Pyramid application with:
@@ -66,33 +70,33 @@ Steps
$ $VENV/bin/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.
+#. 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.
-#. 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.
+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.
+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 request.
+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 request.
-Flash messages are a technique where messages can be stored between
-requests, using sessions, then removed when they finally get displayed.
+Flash messages are a technique where messages can be stored between requests,
+using sessions, then removed when they finally get displayed.
.. seealso::
:ref:`sessions_chapter`,
diff --git a/docs/quick_tutorial/static_assets.rst b/docs/quick_tutorial/static_assets.rst
index 3a7496ec7..65b34f8f9 100644
--- a/docs/quick_tutorial/static_assets.rst
+++ b/docs/quick_tutorial/static_assets.rst
@@ -4,16 +4,17 @@
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.
+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
+- Publish a directory of static assets at a URL.
+
+- Use Pyramid to help generate URLs to files in that directory.
-- Use Pyramid to help generate URLs to files in that directory
Steps
=====
@@ -23,7 +24,7 @@ Steps
.. code-block:: bash
$ cd ..; cp -r view_classes static_assets; cd static_assets
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. We add a call ``config.add_static_view`` in
``static_assets/tutorial/__init__.py``:
@@ -37,8 +38,7 @@ Steps
.. literalinclude:: static_assets/tutorial/home.pt
:language: html
-#. Add a CSS file at
- ``static_assets/tutorial/static/app.css``:
+#. Add a CSS file at ``static_assets/tutorial/static/app.css``:
.. literalinclude:: static_assets/tutorial/static/app.css
:language: css
@@ -47,7 +47,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
+ $ $VENV/bin/$VENV/bin/py.test tutorial/tests.py -q
+ ....
+ 4 passed in 0.50 seconds
#. Run your Pyramid application with:
@@ -57,30 +59,31 @@ Steps
#. Open http://localhost:6543/ in your browser and note the new font.
+
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``.
+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:
+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.
+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
+Extra credit
============
#. There is also a ``request.static_path`` API. How does this differ from
diff --git a/docs/quick_tutorial/templating.rst b/docs/quick_tutorial/templating.rst
index cf56d2a96..ec6de98f8 100644
--- a/docs/quick_tutorial/templating.rst
+++ b/docs/quick_tutorial/templating.rst
@@ -4,50 +4,53 @@
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.
+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.
+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, and so on.
+It encourages replaceability. This applies equally to templating, which is
+fortunate: developers have strong views about template languages. As of
+Pyramid 1.5a2, Pyramid doesn't even bundle a template 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. As of Pyramid 1.5a2, Pyramid doesn't even bundle a template
-language!
+It does, however, have strong ties to Jinja2, Mako, and Chameleon. In this step
+we see how to add `pyramid_chameleon
+<https://github.com/Pylons/pyramid_chameleon>`_ to your project, then change
+your views to use templating.
-It does, however, have strong ties to Jinja2, Mako, and Chameleon. In
-this step we see how to add ``pyramid_chameleon`` to your project,
-then change your views to use templating.
Objectives
==========
-- Enable the ``pyramid_chameleon`` Pyramid add-on
+- Enable the ``pyramid_chameleon`` Pyramid add-on.
+
+- Generate HTML from template files.
-- Generate HTML from template files
+- Connect the templates as "renderers" for view code.
-- Connect the templates as "renderers" for view code
+- Change the view code to simply return data.
-- Change the view code to simply return data
Steps
=====
-#. Let's begin by using the previous package as a starting point for a
- new project:
+#. Let's begin by using the previous package as a starting point for a new
+ project:
.. code-block:: bash
$ cd ..; cp -r views templating; cd templating
-#. This step depends on ``pyramid_chameleon``, so add it as a dependency
- in ``templating/setup.py``:
+#. This step depends on ``pyramid_chameleon``, so add it as a dependency in
+ ``templating/setup.py``:
.. literalinclude:: templating/setup.py
:linenos:
@@ -56,10 +59,10 @@ Steps
.. code-block:: bash
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
-#. We need to connect ``pyramid_chameleon`` as a renderer by making a
- call in the setup of ``templating/tutorial/__init__.py``:
+#. We need to connect ``pyramid_chameleon`` as a renderer by making a call in
+ the setup of ``templating/tutorial/__init__.py``:
.. literalinclude:: templating/tutorial/__init__.py
:linenos:
@@ -74,14 +77,13 @@ Steps
.. literalinclude:: templating/tutorial/home.pt
:language: html
-#. For convenience, change ``templating/development.ini`` to reload
- templates automatically with ``pyramid.reload_templates``:
+#. 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:
+#. Our unit tests in ``templating/tutorial/tests.py`` can focus on data:
.. literalinclude:: templating/tutorial/tests.py
:linenos:
@@ -90,13 +92,9 @@ Steps
.. code-block:: bash
-
- $ $VENV/bin/nosetests tutorial
- .
- ----------------------------------------------------------------------
- Ran 4 tests in 0.141s
-
- OK
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ....
+ 4 passed in 0.46 seconds
#. Run your Pyramid application with:
@@ -104,20 +102,19 @@ Steps
$ $VENV/bin/pserve development.ini --reload
-#. Open http://localhost:6543/ and http://localhost:6543/howdy
- in your browser.
+#. 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:`renderer` that points
-to 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.
+Ahh, that looks better. We have a view that is focused on Python code. Our
+``@view_config`` decorator specifies a :term:`renderer` that points to 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.
+Note the effect on testing. We can focus on having a data-oriented contract
+with our view code.
.. seealso:: :ref:`templates_chapter`, :ref:`debugging_templates`, and
:ref:`available_template_system_bindings`.
diff --git a/docs/quick_tutorial/tutorial_approach.rst b/docs/quick_tutorial/tutorial_approach.rst
index 204d388b0..49a6bfd85 100644
--- a/docs/quick_tutorial/tutorial_approach.rst
+++ b/docs/quick_tutorial/tutorial_approach.rst
@@ -2,44 +2,46 @@
Tutorial Approach
=================
-This tutorial uses conventions to keep the introduction focused and
-concise. Details, references, and deeper discussions are mentioned in
-"See also" notes.
+This tutorial uses conventions to keep the introduction focused and concise.
+Details, references, and deeper discussions are mentioned in "See also" notes.
.. seealso:: This is an example "See also" note.
-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.
+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::
+To successfully run each step:
- $ cd request_response
- $ $VENV/bin/python setup.py develop
+.. code-block:: bash
-...and repeat for each step you would like to work on. In most cases we
-will start with the results of an earlier step.
+ $ cd request_response
+ $ $VENV/bin/pip install -e .
-Directory Tree
+...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::
-
- quicktutorial/
- request_response/
- development.ini
- setup.py
- tutorial/
- __init__.py
- home.pt
- tests.py
- views.py
-
-Each of the first-level directories (e.g. ``request_response``) is a
-*Python project* (except, as noted, the ``hello_world`` step.) The
-``tutorial`` directory is a *Python package*. At the end of each step,
-we copy a previous directory into a new directory to use as a starting
-point.
+As we develop our tutorial, our directory tree will resemble the structure
+below:
+
+.. code-block:: text
+
+ quick_tutorial
+ │── env
+ `── request_response
+ `── tutorial
+ │ │── __init__.py
+ │ │── tests.py
+ │ `── views.py
+ │── development.ini
+ `── setup.py
+
+Each of the first-level directories (e.g., ``request_response``) is a *Python
+project* (except as noted for the ``hello_world`` step). The ``tutorial``
+directory is a *Python package*. At the end of each step, we copy a previous
+directory into a new directory to use as a starting point.
diff --git a/docs/quick_tutorial/unit_testing.rst b/docs/quick_tutorial/unit_testing.rst
index 4cb7ef714..7c85d5289 100644
--- a/docs/quick_tutorial/unit_testing.rst
+++ b/docs/quick_tutorial/unit_testing.rst
@@ -1,55 +1,56 @@
.. _qtut_unit_testing:
-===========================
-05: Unit Tests and ``nose``
-===========================
+=============================
+05: Unit Tests and ``pytest``
+=============================
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.
+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 :ref:`pytest <pytest:features>`, have
+extended this framework with alternative test runners that provide more
+convenience and functionality. The Pyramid developers use ``pytest``, which
+we'll 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 `pytest-cov
+<http://pytest-cov.readthedocs.org/en/latest/>`_ for another section.
+
Objectives
==========
-- Write unit tests that ensure the quality of our code
+- Write unit tests that ensure the quality of our code.
+
+- Install a Python package (``pytest``) which helps in our testing.
-- 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:
+#. First we copy the results of the previous step, as well as install the
+ ``pytest`` package:
.. code-block:: bash
$ cd ..; cp -r debugtoolbar unit_testing; cd unit_testing
- $ $VENV/bin/python setup.py develop
- $ $VENV/bin/easy_install nose
+ $ $VENV/bin/pip install -e .
+ $ $VENV/bin/pip install pytest
#. Now we write a simple unit test in ``unit_testing/tutorial/tests.py``:
@@ -61,54 +62,51 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
+ $ $VENV/bin/py.test tutorial/tests.py -q
.
- ----------------------------------------------------------------------
- Ran 1 test in 0.141s
+ 1 passed in 0.14 seconds
- 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.
+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 expect.
-The ``tests.TutorialViewTests.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.
+The ``tests.TutorialViewTests.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.
+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
+
+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.
+#. Change the test to assert that the response status code should be ``404``
+ (meaning, not found). Run ``py.test`` 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.
+#. 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.
+ 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?
diff --git a/docs/quick_tutorial/view_classes.rst b/docs/quick_tutorial/view_classes.rst
index 50a7ee0af..05d97a9b1 100644
--- a/docs/quick_tutorial/view_classes.rst
+++ b/docs/quick_tutorial/view_classes.rst
@@ -4,55 +4,55 @@
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.
+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 <class_as_view>` makes sense:
+So far our views have been simple, free-standing functions. Many times your
+views are related to one another. They may be different ways to look at or work
+on the same data, or be a REST API that handles multiple operations. Grouping
+these views together as a :ref:`view class <class_as_view>` makes sense:
+
+- Group views.
-- Group views
+- Centralize some repetitive defaults.
-- Centralize some repetitive defaults
+- Share some state and helpers.
-- 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.
-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
+- Group related views into a view class.
+
+- Centralize configuration with class-level ``@view_defaults``.
-- Centralize configuration with class-level ``@view_defaults``
Steps
=====
-
#. First we copy the results of the previous step:
.. code-block:: bash
$ cd ..; cp -r templating view_classes; cd view_classes
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
-#. Our ``view_classes/tutorial/views.py`` now has a view class with
- our two views:
+#. 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 them to import the view class and make an instance
- before getting a response:
+#. Our unit tests in ``view_classes/tutorial/tests.py`` don't run, so let's
+ modify them to import the view class, and make an instance before getting a
+ response:
.. literalinclude:: view_classes/tutorial/tests.py
:linenos:
@@ -62,12 +62,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
- .
- ----------------------------------------------------------------------
- Ran 4 tests in 0.141s
-
- OK
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ....
+ 4 passed in 0.34 seconds
#. Run your Pyramid application with:
@@ -75,24 +72,23 @@ Steps
$ $VENV/bin/pserve development.ini --reload
-#. Open http://localhost:6543/ and http://localhost:6543/howdy
- in your browser.
+#. 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 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.
+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
+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:`class_as_view`
diff --git a/docs/quick_tutorial/views.rst b/docs/quick_tutorial/views.rst
index 6728925fd..edbe4b2ff 100644
--- a/docs/quick_tutorial/views.rst
+++ b/docs/quick_tutorial/views.rst
@@ -6,12 +6,12 @@
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.
+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:
@@ -23,27 +23,29 @@ So far our examples place everything in one file:
- 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.
+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 set up the views.
+Let's also add a second view and update our tests.
+
Objectives
==========
-- Views in a module that is scanned by the configurator
+- Move views into a module that is scanned by the configurator.
+
+- Create decorators that do declarative configuration.
-- 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:
+#. Let's begin by using the previous package as a starting point for a new
+ distribution, then making it active:
.. code-block:: bash
$ cd ..; cp -r functional_testing views; cd views
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
#. Our ``views/tutorial/__init__.py`` gets a lot shorter:
@@ -66,12 +68,9 @@ Steps
.. code-block:: bash
- $ $VENV/bin/nosetests tutorial
- .
- ----------------------------------------------------------------------
- Ran 4 tests in 0.141s
-
- OK
+ $ $VENV/bin/py.test tutorial/tests.py -q
+ ....
+ 4 passed in 0.28 seconds
#. Run your Pyramid application with:
@@ -82,41 +81,41 @@ Steps
#. 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:`imperative configuration`, such as the
-``config.add_view`` in the previous example. You can also use
-:term:`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
+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 two 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:`imperative
+configuration`, such as the ``config.add_view`` in the previous example. You
+can also use :term:`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``?
+#. Why might ``assertIn`` be a better choice in testing the text in responses
+ than ``assertEqual``?
.. seealso:: :ref:`views_chapter`,
:ref:`view_config_chapter`, and
:ref:`debugging_view_configuration`
-
diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst
index ddd968927..c66786b11 100644
--- a/docs/tutorials/modwsgi/index.rst
+++ b/docs/tutorials/modwsgi/index.rst
@@ -1,7 +1,7 @@
.. _modwsgi_tutorial:
Running a :app:`Pyramid` Application under ``mod_wsgi``
-==========================================================
+=======================================================
:term:`mod_wsgi` is an Apache module developed by Graham Dumpleton.
It allows :term:`WSGI` programs to be served using the Apache web
@@ -18,27 +18,21 @@ specific path information for commands and files.
``mod_wsgi``. If you have experience with :app:`Pyramid` and ``mod_wsgi``
on Windows systems, please help us document this experience by submitting
documentation to the `Pylons-devel maillist
- <http://groups.google.com/group/pylons-devel>`_.
+ <https://groups.google.com/forum/#!forum/pylons-devel>`_.
#. The tutorial assumes you have Apache already installed on your
system. If you do not, install Apache 2.X for your platform in
whatever manner makes sense.
+#. It is also assumed that you have satisfied the
+ :ref:`requirements-for-installing-packages`.
+
#. Once you have Apache installed, install ``mod_wsgi``. Use the
(excellent) `installation instructions
- <http://code.google.com/p/modwsgi/wiki/InstallationInstructions>`_
+ <https://code.google.com/archive/p/modwsgi/wikis/InstallationInstructions.wiki>`_
for your platform into your system's Apache installation.
-#. Install :term:`virtualenv` into the Python which mod_wsgi will
- run using the ``easy_install`` program.
-
- .. code-block:: text
-
- $ sudo /usr/bin/easy_install-2.6 virtualenv
-
- This command may need to be performed as the root user.
-
-#. Create a :term:`virtualenv` which we'll use to install our
+#. Create a :term:`virtual environment` which we'll use to install our
application.
.. code-block:: text
@@ -46,15 +40,15 @@ specific path information for commands and files.
$ cd ~
$ mkdir modwsgi
$ cd modwsgi
- $ /usr/local/bin/virtualenv env
+ $ python3 -m venv env
-#. Install :app:`Pyramid` into the newly created virtualenv:
+#. Install :app:`Pyramid` into the newly created virtual environment:
- .. code-block:: text
+ .. parsed-literal::
$ cd ~/modwsgi/env
- $ $VENV/bin/easy_install pyramid
-
+ $ $VENV/bin/pip install "pyramid==\ |release|\ "
+
#. Create and install your :app:`Pyramid` application. For the purposes of
this tutorial, we'll just be using the ``pyramid_starter`` application as
a baseline application. Substitute your existing :app:`Pyramid`
@@ -65,9 +59,9 @@ specific path information for commands and files.
$ cd ~/modwsgi/env
$ $VENV/bin/pcreate -s starter myapp
$ cd myapp
- $ $VENV/bin/python setup.py install
+ $ $VENV/bin/pip install -e .
-#. Within the virtualenv directory (``~/modwsgi/env``), create a
+#. Within the virtual environment directory (``~/modwsgi/env``), create a
script named ``pyramid.wsgi``. Give it these contents:
.. code-block:: python
@@ -107,7 +101,7 @@ specific path information for commands and files.
WSGIApplicationGroup %{GLOBAL}
WSGIPassAuthorization On
WSGIDaemonProcess pyramid user=chrism group=staff threads=4 \
- python-path=/Users/chrism/modwsgi/env/lib/python2.6/site-packages
+ python-path=/Users/chrism/modwsgi/env/lib/python2.7/site-packages
WSGIScriptAlias /myapp /Users/chrism/modwsgi/env/pyramid.wsgi
<Directory /Users/chrism/modwsgi/env>
@@ -125,10 +119,8 @@ specific path information for commands and files.
#. Visit ``http://localhost/myapp`` in a browser. You should see the
sample application rendered in your browser.
-:term:`mod_wsgi` has many knobs and a great variety of deployment
-modes. This is just one representation of how you might use it to
-serve up a :app:`Pyramid` application. See the `mod_wsgi
-configuration documentation
-<http://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines>`_ for
-more in-depth configuration information.
-
+:term:`mod_wsgi` has many knobs and a great variety of deployment modes. This
+is just one representation of how you might use it to serve up a :app:`Pyramid`
+application. See the `mod_wsgi configuration documentation
+<https://code.google.com/archive/p/modwsgi/wikis/ConfigurationGuidelines.wiki>`_
+for more in-depth configuration information.
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index b0a8c155d..44097b35b 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -5,12 +5,12 @@ Adding authorization
====================
:app:`Pyramid` provides facilities for :term:`authentication` and
-::term:`authorization`. We'll make use of both features to provide security
-:to our application. Our application currently allows anyone with access to
-:the server to view, edit, and add pages to our wiki. We'll change that to
-:allow only people who are members of a *group* named ``group:editors`` to add
-:and edit wiki pages but we'll continue allowing anyone with access to the
-:server to view pages.
+:term:`authorization`. We'll make use of both features to provide security to
+our application. Our application currently allows anyone with access to the
+server to view, edit, and add pages to our wiki. We'll change that to allow
+only people who are members of a *group* named ``group:editors`` to add and
+edit wiki pages, but we'll continue allowing anyone with access to the server
+to view pages.
We will also add a login page and a logout link on all the pages. The login
page will be shown when a user is denied access to any of the views that
@@ -41,7 +41,7 @@ Access control
Add users and groups
~~~~~~~~~~~~~~~~~~~~
-Create a new ``tutorial/tutorial/security.py`` module with the
+Create a new ``tutorial/security.py`` module with the
following content:
.. literalinclude:: src/authorization/tutorial/security.py
@@ -67,7 +67,7 @@ database, but here we use "dummy" data to represent user and groups sources.
Add an ACL
~~~~~~~~~~
-Open ``tutorial/tutorial/models.py`` and add the following import
+Open ``tutorial/models.py`` and add the following import
statement at the head:
.. literalinclude:: src/authorization/tutorial/models.py
@@ -109,7 +109,7 @@ more information about what an :term:`ACL` represents.
Add authentication and authorization policies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/tutorial/__init__.py`` and add the highlighted import
+Open ``tutorial/__init__.py`` and add the highlighted import
statements:
.. literalinclude:: src/authorization/tutorial/__init__.py
@@ -142,7 +142,7 @@ machinery represented by this policy: it is required. The ``callback`` is the
Add permission declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/tutorial/views.py`` and add a ``permission='edit'`` parameter
+Open ``tutorial/views.py`` and add a ``permission='edit'`` parameter
to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
.. literalinclude:: src/authorization/tutorial/views.py
@@ -196,7 +196,7 @@ link to it. This view will clear the credentials of the logged in user and
redirect back to the front page.
Add the following import statements to the head of
-``tutorial/tutorial/views.py``:
+``tutorial/views.py``:
.. literalinclude:: src/authorization/tutorial/views.py
:lines: 6-17
@@ -236,7 +236,7 @@ it with the ``logout`` route. It will be invoked when we visit ``/logout``.
Add the ``login.pt`` Template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Create ``tutorial/tutorial/templates/login.pt`` with the following content:
+Create ``tutorial/templates/login.pt`` with the following content:
.. literalinclude:: src/authorization/tutorial/templates/login.pt
:language: html
@@ -247,8 +247,8 @@ The above template is referenced in the login view that we just added in
Return a ``logged_in`` flag to the renderer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/tutorial/views.py`` again. Add a ``logged_in`` parameter to
-the return value of ``view_page()``, ``edit_page()``, and ``add_page()`` as
+Open ``tutorial/views.py`` again. Add a ``logged_in`` parameter to
+the return value of ``view_page()``, ``add_page()``, and ``edit_page()`` as
follows:
.. literalinclude:: src/authorization/tutorial/views.py
@@ -262,7 +262,7 @@ follows:
:language: python
.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 75-77
+ :lines: 78-80
:emphasize-lines: 2-3
:language: python
@@ -274,8 +274,8 @@ the user is not authenticated, or a userid if the user is authenticated.
Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/tutorial/templates/edit.pt`` and
-``tutorial/tutorial/templates/view.pt`` and add the following code as
+Open ``tutorial/templates/edit.pt`` and
+``tutorial/templates/view.pt`` and add the following code as
indicated by the highlighted lines.
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
@@ -291,7 +291,7 @@ a user is not authenticated.
Reviewing our changes
---------------------
-Our ``tutorial/tutorial/__init__.py`` will look like this when we're done:
+Our ``tutorial/__init__.py`` will look like this when we're done:
.. literalinclude:: src/authorization/tutorial/__init__.py
:linenos:
@@ -300,7 +300,7 @@ Our ``tutorial/tutorial/__init__.py`` will look like this when we're done:
Only the highlighted lines need to be added or edited.
-Our ``tutorial/tutorial/models.py`` will look like this when we're done:
+Our ``tutorial/models.py`` will look like this when we're done:
.. literalinclude:: src/authorization/tutorial/models.py
:linenos:
@@ -309,7 +309,7 @@ Our ``tutorial/tutorial/models.py`` will look like this when we're done:
Only the highlighted lines need to be added or edited.
-Our ``tutorial/tutorial/views.py`` will look like this when we're done:
+Our ``tutorial/views.py`` will look like this when we're done:
.. literalinclude:: src/authorization/tutorial/views.py
:linenos:
@@ -318,7 +318,7 @@ Our ``tutorial/tutorial/views.py`` will look like this when we're done:
Only the highlighted lines need to be added or edited.
-Our ``tutorial/tutorial/templates/edit.pt`` template will look like this when
+Our ``tutorial/templates/edit.pt`` template will look like this when
we're done:
.. literalinclude:: src/authorization/tutorial/templates/edit.pt
@@ -328,7 +328,7 @@ we're done:
Only the highlighted lines need to be added or edited.
-Our ``tutorial/tutorial/templates/view.pt`` template will look like this when
+Our ``tutorial/templates/view.pt`` template will look like this when
we're done:
.. literalinclude:: src/authorization/tutorial/templates/view.pt
diff --git a/docs/tutorials/wiki/background.rst b/docs/tutorials/wiki/background.rst
index 6bbd5026e..31dcd6b53 100644
--- a/docs/tutorials/wiki/background.rst
+++ b/docs/tutorials/wiki/background.rst
@@ -1,3 +1,5 @@
+.. _wiki_background:
+
==========
Background
==========
diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst
index 0484ebf17..20bfdf754 100644
--- a/docs/tutorials/wiki/basiclayout.rst
+++ b/docs/tutorials/wiki/basiclayout.rst
@@ -1,3 +1,5 @@
+.. _wiki_basic_layout:
+
============
Basic Layout
============
@@ -12,21 +14,22 @@ Application configuration with ``__init__.py``
A directory on disk can be turned into a Python :term:`package` by containing
an ``__init__.py`` file. Even if empty, this marks a directory as a Python
-package. We use ``__init__.py`` both as a marker, indicating the directory
-in which it's contained is a package, and to contain application configuration
+package. We use ``__init__.py`` both as a marker, indicating the directory in
+which it's contained is a package, and to contain application configuration
code.
When you run the application using the ``pserve`` command using the
``development.ini`` generated configuration file, the application
-configuration points at a Setuptools *entry point* described as
+configuration points at a setuptools *entry point* described as
``egg:tutorial``. In our application, because the application's ``setup.py``
file says so, this entry point happens to be the ``main`` function within the
-file named ``__init__.py``. Let's take a look at the code and describe what
-it does:
+file named ``__init__.py``.
+
+Open ``tutorial/__init__.py``. It should already contain the following:
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :linenos:
+ :language: py
#. *Lines 1-3*. Perform some dependency imports.
@@ -81,9 +84,9 @@ resource objects, each of which also happens to be a domain model object.
Here is the source for ``models.py``:
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/models.py
+ :linenos:
+ :language: python
#. *Lines 4-5*. The ``MyModel`` :term:`resource` class is implemented here.
Instances of this class are capable of being persisted in :term:`ZODB`
@@ -113,9 +116,9 @@ the URL ``http://localhost:6543/``.
Here is the source for ``views.py``:
- .. literalinclude:: src/basiclayout/tutorial/views.py
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/views.py
+ :linenos:
+ :language: python
Let's try to understand the components in this module:
@@ -169,7 +172,7 @@ The ``development.ini`` (in the tutorial :term:`project` directory, as
opposed to the tutorial :term:`package` directory) looks like this:
.. literalinclude:: src/basiclayout/development.ini
- :language: ini
+ :language: ini
Note the existence of a ``[app:main]`` section which specifies our WSGI
application. Our ZODB database settings are specified as the
diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst
index 859e902ab..73dce14d5 100644
--- a/docs/tutorials/wiki/definingmodels.rst
+++ b/docs/tutorials/wiki/definingmodels.rst
@@ -1,9 +1,11 @@
+.. _wiki_defining_the_domain_model:
+
=========================
Defining the Domain Model
=========================
-The first change we'll make to our stock pcreate-generated application will be
-to define two :term:`resource` constructors, one representing a wiki page,
+The first change we'll make to our stock ``pcreate``-generated application will
+be to define two :term:`resource` constructors, one representing a wiki page,
and another representing the wiki as a mapping of wiki page names to page
objects. We'll do this inside our ``models.py`` file.
@@ -38,8 +40,7 @@ Edit ``models.py``
or they may live in a Python subpackage of your application package named
``models``, but this is only by convention.
-Open ``tutorial/tutorial/models.py`` file and edit it to look like the
-following:
+Open ``tutorial/models.py`` file and edit it to look like the following:
.. literalinclude:: src/models/tutorial/models.py
:linenos:
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index ed173a706..ac94d8059 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -1,3 +1,5 @@
+.. _wiki_defining_views:
+
==============
Defining Views
==============
@@ -41,7 +43,7 @@ We need to add a dependency on the ``docutils`` package to our ``tutorial``
package's ``setup.py`` file by assigning this dependency to the ``requires``
parameter in the ``setup()`` function.
-Open ``tutorial/setup.py`` and edit it to look like the following:
+Open ``setup.py`` and edit it to look like the following:
.. literalinclude:: src/views/setup.py
:linenos:
@@ -50,40 +52,44 @@ Open ``tutorial/setup.py`` and edit it to look like the following:
Only the highlighted line needs to be added.
-Running ``setup.py develop``
+
+Running ``pip install -e .``
============================
-Since a new software dependency was added, you will need to run ``python
-setup.py develop`` again inside the root of the ``tutorial`` package to obtain
-and register the newly added dependency distribution.
+Since a new software dependency was added, you will need to run ``pip install
+-e .`` again inside the root of the ``tutorial`` package to obtain and register
+the newly added dependency distribution.
Make sure your current working directory is the root of the project (the
directory in which ``setup.py`` lives) and execute the following command.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ cd tutorial
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
On Windows:
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut> cd tutorial
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
Success executing this command will end with a line to the console something
-like::
+like:
+
+.. code-block:: text
+
+ Successfully installed docutils-0.12 tutorial-0.0
- Finished processing dependencies for tutorial==0.0
Adding view functions in ``views.py``
=====================================
-It's time for a major change. Open ``tutorial/tutorial/views.py`` and edit it
-to look like the following:
+It's time for a major change. Open ``tutorial/views.py`` and edit it to look
+like the following:
.. literalinclude:: src/views/tutorial/views.py
:linenos:
@@ -310,7 +316,7 @@ extension to be recognized as such.
The ``view.pt`` template
------------------------
-Create ``tutorial/tutorial/templates/view.pt`` and add the following
+Create ``tutorial/templates/view.pt`` and add the following
content:
.. literalinclude:: src/views/tutorial/templates/view.pt
@@ -329,8 +335,7 @@ wiki page. It includes:
The ``edit.pt`` template
------------------------
-Create ``tutorial/tutorial/templates/edit.pt`` and add the following
-content:
+Create ``tutorial/templates/edit.pt`` and add the following content:
.. literalinclude:: src/views/tutorial/templates/edit.pt
:linenos:
@@ -352,6 +357,7 @@ The form POSTs back to the ``save_url`` argument supplied by the view (line
See :ref:`renderer_system_values` for information about other names that
are available by default when a template is used as a renderer.
+
Static assets
-------------
@@ -367,6 +373,7 @@ subdirectories) and are just referred to by URL or by using the convenience
method ``static_url``, e.g.,
``request.static_url('<package>:static/foo.css')`` within templates.
+
Viewing the application in a browser
====================================
diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst
index 49c30d29a..f2a02176b 100644
--- a/docs/tutorials/wiki/design.rst
+++ b/docs/tutorials/wiki/design.rst
@@ -1,6 +1,8 @@
-==========
+.. _wiki_design:
+
+======
Design
-==========
+======
Following is a quick overview of the design of our wiki application, to help
us understand the changes that we will be making as we work through the
diff --git a/docs/tutorials/wiki/distributing.rst b/docs/tutorials/wiki/distributing.rst
index fee50a1cf..386b880e6 100644
--- a/docs/tutorials/wiki/distributing.rst
+++ b/docs/tutorials/wiki/distributing.rst
@@ -1,3 +1,5 @@
+.. _wiki_distributing_your_application:
+
=============================
Distributing Your Application
=============================
@@ -5,18 +7,18 @@ Distributing Your Application
Once your application works properly, you can create a "tarball" from it by
using the ``setup.py sdist`` command. The following commands assume your
current working directory is the ``tutorial`` package we've created and that
-the parent directory of the ``tutorial`` package is a virtualenv representing
-a :app:`Pyramid` environment.
+the parent directory of the ``tutorial`` package is a virtual environment
+representing a :app:`Pyramid` environment.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/python setup.py sdist
On Windows:
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut> %VENV%\Scripts\python setup.py sdist
@@ -25,16 +27,15 @@ The output of such a command will be something like:
.. code-block:: text
running sdist
- # .. more output ..
+ # more output
creating dist
- tar -cf dist/tutorial-0.0.tar tutorial-0.0
- gzip -f9 dist/tutorial-0.0.tar
+ Creating tar archive
removing 'tutorial-0.0' (and everything under it)
Note that this command creates a tarball in the "dist" subdirectory named
``tutorial-0.0.tar.gz``. You can send this file to your friends to show them
your cool new application. They should be able to install it by pointing the
-``easy_install`` command directly at it. Or you can upload it to `PyPI
-<http://pypi.python.org>`_ and share it with the rest of the world, where it
-can be downloaded via ``easy_install`` remotely like any other package people
+``pip install .`` command directly at it. Or you can upload it to `PyPI
+<https://pypi.python.org/pypi>`_ and share it with the rest of the world, where
+it can be downloaded via ``pip install`` remotely like any other package people
download from PyPI.
diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst
index 89c026dac..7808c7623 100644
--- a/docs/tutorials/wiki/index.rst
+++ b/docs/tutorials/wiki/index.rst
@@ -26,4 +26,3 @@ which corresponds to the same location if you have Pyramid sources.
authorization
tests
distributing
-
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index 20df389c6..6172b122b 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -1,17 +1,19 @@
+.. _wiki_installation:
+
============
Installation
============
Before you begin
-================
+----------------
This tutorial assumes that you have already followed the steps in
-:ref:`installing_chapter`, except **do not create a virtualenv or install
-Pyramid**. Thereby you will satisfy the following requirements.
+:ref:`installing_chapter`, except **do not create a virtual environment or
+install Pyramid**. Thereby you will satisfy the following requirements.
+
+* A Python interpreter is installed on your operating system.
+* You've satisfied the :ref:`requirements-for-installing-packages`.
-* Python interpreter is installed on your operating system
-* :term:`setuptools` or :term:`distribute` is installed
-* :term:`virtualenv` is installed
Create directory to contain the project
---------------------------------------
@@ -21,257 +23,366 @@ We need a workspace for our project files.
On UNIX
^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ mkdir ~/pyramidtut
On Windows
^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\> mkdir pyramidtut
+
Create and use a virtual Python environment
-------------------------------------------
-Next let's create a `virtualenv` workspace for our project. We will
-use the `VENV` environment variable instead of the absolute path of the
-virtual environment.
+Next let's create a virtual environment workspace for our project. We will use
+the ``VENV`` environment variable instead of the absolute path of the virtual
+environment.
On UNIX
^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ export VENV=~/pyramidtut
- $ virtualenv $VENV
- New python executable in /home/foo/env/bin/python
- Installing setuptools.............done.
+ $ python3 -m venv $VENV
On Windows
^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\> set VENV=c:\pyramidtut
-Versions of Python use different paths, so you will need to adjust the
+Each version of Python uses different paths, so you will need to adjust the
path to the command for your Python version.
Python 2.7:
-.. code-block:: text
+.. code-block:: doscon
c:\> c:\Python27\Scripts\virtualenv %VENV%
-Python 3.2:
+Python 3.5:
-.. code-block:: text
+.. code-block:: doscon
+
+ c:\> c:\Python35\Scripts\python -m venv %VENV%
- c:\> c:\Python32\Scripts\virtualenv %VENV%
-Install Pyramid and tutorial dependencies into the virtual Python environment
------------------------------------------------------------------------------
+Upgrade ``pip`` and ``setuptools`` in the virtual environment
+-------------------------------------------------------------
On UNIX
^^^^^^^
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/easy_install docutils pyramid_tm pyramid_zodbconn \
- pyramid_debugtoolbar nose coverage
+ $ $VENV/bin/pip install --upgrade pip setuptools
On Windows
^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
+
+ c:\> %VENV%\Scripts\pip install --upgrade pip setuptools
+
+
+Install Pyramid into the virtual Python environment
+---------------------------------------------------
+
+On UNIX
+^^^^^^^
+
+.. parsed-literal::
+
+ $ $VENV/bin/pip install "pyramid==\ |release|\ "
+
+On Windows
+^^^^^^^^^^
+
+.. parsed-literal::
- c:\> %VENV%\Scripts\easy_install docutils pyramid_tm pyramid_zodbconn \
- pyramid_debugtoolbar nose coverage
+ c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
-Change Directory to Your Virtual Python Environment
+
+Change directory to your virtual Python environment
---------------------------------------------------
-Change directory to the ``pyramidtut`` directory.
+Change directory to the ``pyramidtut`` directory, which is both your workspace
+and your virtual environment.
On UNIX
^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ cd pyramidtut
On Windows
^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\> cd pyramidtut
+
.. _making_a_project:
Making a project
-================
+----------------
Your next step is to create a project. For this tutorial, we will use
the :term:`scaffold` named ``zodb``, which generates an application
that uses :term:`ZODB` and :term:`traversal`.
-:app:`Pyramid` supplies a variety of scaffolds to generate sample
-projects. We will use `pcreate`—a script that comes with Pyramid to
-quickly and easily generate scaffolds, usually with a single command—to
-create the scaffold for our project.
+:app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We
+will use ``pcreate``, a script that comes with Pyramid, to create our project
+using a scaffold.
-By passing `zodb` into the `pcreate` command, the script creates
-the files needed to use ZODB. By passing in our application name
-`tutorial`, the script inserts that application name into all the
-required files.
+By passing ``zodb`` into the ``pcreate`` command, the script creates the files
+needed to use ZODB. By passing in our application name ``tutorial``, the script
+inserts that application name into all the required files.
The below instructions assume your current working directory is "pyramidtut".
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/pcreate -s zodb tutorial
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut> %VENV%\Scripts\pcreate -s zodb tutorial
-.. note:: If you are using Windows, the ``zodb``
- scaffold may not deal gracefully with installation into a
- location that contains spaces in the path. If you experience
- startup problems, try putting both the virtualenv and the project
- into directories that do not contain spaces in their paths.
+.. note:: If you are using Windows, the ``zodb`` scaffold may not deal
+ gracefully with installation into a location that contains spaces in the
+ path. If you experience startup problems, try putting both the virtual
+ environment and the project into directories that do not contain spaces in
+ their paths.
+
.. _installing_project_in_dev_mode_zodb:
Installing the project in development mode
-==========================================
+------------------------------------------
-In order to do development on the project easily, you must "register"
-the project as a development egg in your workspace using the
-``setup.py develop`` command. In order to do so, cd to the `tutorial`
-directory you created in :ref:`making_a_project`, and run the
-``setup.py develop`` command using the virtualenv Python interpreter.
+In order to do development on the project easily, you must "register" the
+project as a development egg in your workspace using the ``pip install -e .``
+command. In order to do so, change directory to the ``tutorial`` directory that
+you created in :ref:`making_a_project`, and run the ``pip install -e .``
+command using the virtual environment Python interpreter.
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ cd tutorial
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut> cd tutorial
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
+
+The console will show ``pip`` checking for packages and installing missing
+packages. Success executing this command will show a line like the following:
+
+.. code-block:: bash
+
+ Successfully installed BTrees-4.2.0 Chameleon-2.24 Mako-1.0.4 \
+ MarkupSafe-0.23 Pygments-2.1.3 ZConfig-3.1.0 ZEO-4.2.0b1 ZODB-4.2.0 \
+ ZODB3-3.11.0 mock-2.0.0 pbr-1.8.1 persistent-4.1.1 pyramid-chameleon-0.3 \
+ pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 pyramid-tm-0.12.1 \
+ pyramid-zodbconn-0.7 six-1.10.0 transaction-1.4.4 tutorial waitress-0.8.10 \
+ zc.lockfile-1.1.0 zdaemon-4.1.0 zodbpickle-0.6.0 zodburi-2.0
+
+
+.. _install-testing-requirements-zodb:
+
+Install testing requirements
+----------------------------
+
+In order to run tests, we need to install the testing requirements. This is
+done through our project's ``setup.py`` file, in the ``tests_require`` and
+``extras_require`` stanzas, and by issuing the command below for your
+operating system.
+
+.. literalinclude:: src/installation/setup.py
+ :language: python
+ :linenos:
+ :lineno-start: 22
+ :lines: 22-26
+
+.. literalinclude:: src/installation/setup.py
+ :language: python
+ :linenos:
+ :lineno-start: 45
+ :lines: 45-47
+
+On UNIX
+^^^^^^^
-The console will show `setup.py` checking for packages and installing
-missing packages. Success executing this command will show a line like
-the following::
+.. code-block:: bash
+
+ $ $VENV/bin/pip install -e ".[testing]"
+
+On Windows
+^^^^^^^^^^
+
+.. code-block:: doscon
+
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e ".[testing]"
- Finished processing dependencies for tutorial==0.0
.. _running_tests:
Run the tests
-=============
+-------------
-After you've installed the project in development mode, you may run
-the tests for the project.
+After you've installed the project in development mode as well as the testing
+requirements, you may run the tests for the project. The following commands
+provide options to py.test that specify the module for which its tests shall be
+run, and to run py.test in quiet mode.
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/python setup.py test -q
+ $ $VENV/bin/py.test -q
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py test -q
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q
-For a successful test run, you should see output that ends like this::
+For a successful test run, you should see output that ends like this:
+
+.. code-block:: bash
+
+ .
+ 1 passed in 0.24 seconds
- .
- ----------------------------------------------------------------------
- Ran 1 test in 0.094s
-
- OK
Expose test coverage information
-================================
+--------------------------------
-You can run the ``nosetests`` command to see test coverage
-information. This runs the tests in the same way that ``setup.py
-test`` does but provides additional "coverage" information, exposing
-which lines of your project are "covered" (or not covered) by the
+You can run the ``py.test`` command to see test coverage information. This
+runs the tests in the same way that ``py.test`` does, but provides additional
+"coverage" information, exposing which lines of your project are covered by the
tests.
+We've already installed the ``pytest-cov`` package into our virtual
+environment, so we can run the tests with coverage.
+
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/nosetests --cover-package=tutorial --cover-erase --with-coverage
+ $ $VENV/bin/py.test --cov --cov-report=term-missing
On Windows
-----------
+^^^^^^^^^^
+
+.. code-block:: doscon
+
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov \
+ --cov-report=term-missing
+
+If successful, you will see output something like this:
+
+.. code-block:: bash
+
+ ======================== test session starts ========================
+ platform Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
+ rootdir: /Users/stevepiercy/projects/pyramidtut/tutorial, inifile:
+ plugins: cov-2.2.1
+ collected 1 items
+
+ tutorial/tests.py .
+ ------------------ coverage: platform Python 3.5.1 ------------------
+ Name Stmts Miss Cover Missing
+ ----------------------------------------------------
+ tutorial/__init__.py 12 7 42% 7-8, 14-18
+ tutorial/models.py 10 6 40% 9-14
+ tutorial/tests.py 12 0 100%
+ tutorial/views.py 4 0 100%
+ ----------------------------------------------------
+ TOTAL 38 13 66%
+
+ ===================== 1 passed in 0.31 seconds ======================
+
+Our package doesn't quite have 100% test coverage.
-.. code-block:: text
- c:\pyramidtut\tutorial> %VENV%\Scripts\nosetests --cover-package=tutorial \
- --cover-erase --with-coverage
+.. _test_and_coverage_scaffold_defaults_zodb:
-If successful, you will see output something like this::
+Test and coverage scaffold defaults
+-----------------------------------
- .
- Name Stmts Miss Cover Missing
- --------------------------------------------------
- tutorial.py 12 7 42% 7-8, 14-18
- tutorial/models.py 10 6 40% 9-14
- tutorial/views.py 4 0 100%
- --------------------------------------------------
- TOTAL 26 13 50%
- ----------------------------------------------------------------------
- Ran 1 test in 0.392s
+Scaffolds include configuration defaults for ``py.test`` and test coverage.
+These configuration files are ``pytest.ini`` and ``.coveragerc``, located at
+the root of your package. Without these defaults, we would need to specify the
+path to the module on which we want to run tests and coverage.
- OK
+On UNIX
+^^^^^^^
+
+.. code-block:: bash
+
+ $ $VENV/bin/py.test --cov=tutorial tutorial/tests.py -q
+
+On Windows
+^^^^^^^^^^
+
+.. code-block:: doscon
+
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov=tutorial \
+ --cov-report=term-missing tutorial\tests.py -q
+
+py.test follows :ref:`conventions for Python test discovery
+<pytest:test discovery>`, and the configuration defaults from the scaffold
+tell ``py.test`` where to find the module on which we want to run tests and
+coverage.
+
+.. seealso:: See py.test's documentation for :ref:`pytest:usage` or invoke
+ ``py.test -h`` to see its full set of options.
-Looks like our package doesn't quite have 100% test coverage.
.. _wiki-start-the-application:
Start the application
-=====================
+---------------------
Start the application.
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/pserve development.ini --reload
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload
@@ -280,38 +391,61 @@ On Windows
Your OS firewall, if any, may pop up a dialog asking for authorization
to allow python to accept incoming network connections.
-If successful, you will see something like this on your console::
+If successful, you will see something like this on your console:
+
+.. code-block:: text
- Starting subprocess with file monitor
- Starting server in PID 95736.
- serving on http://0.0.0.0:6543
+ Starting subprocess with file monitor
+ Starting server in PID 82349.
+ serving on http://127.0.0.1:6543
This means the server is ready to accept requests.
+
Visit the application in a browser
-==================================
+----------------------------------
-In a browser, visit `http://localhost:6543/ <http://localhost:6543>`_. You
-will see the generated application's default page.
+In a browser, visit http://localhost:6543/. You will see the generated
+application's default page.
One thing you'll notice is the "debug toolbar" icon on right hand side of the
page. You can read more about the purpose of the icon at
:ref:`debug_toolbar`. It allows you to get information about your
application while you develop.
+
Decisions the ``zodb`` scaffold has made for you
-================================================
+------------------------------------------------
Creating a project using the ``zodb`` scaffold makes the following
assumptions:
-- you are willing to use :term:`ZODB` as persistent storage
+- You are willing to use :term:`ZODB` as persistent storage.
-- you are willing to use :term:`traversal` to map URLs to code
+- You are willing to use :term:`traversal` to map URLs to code.
+
+- You want to use pyramid_zodbconn_, pyramid_tm_, and the transaction_ packages
+ to manage connections and transactions with :term:`ZODB`.
+
+- You want to use pyramid_chameleon_ to render your templates. Different
+ templating engines can be used, but we had to choose one to make this
+ tutorial. See :ref:`available_template_system_bindings` for some options.
.. note::
- :app:`Pyramid` supports any persistent storage mechanism (e.g., a SQL
- database or filesystem files). It also supports an additional
- mechanism to map URLs to code (:term:`URL dispatch`). However, for the
- purposes of this tutorial, we'll only be using traversal and ZODB.
+ :app:`Pyramid` supports any persistent storage mechanism (e.g., an SQL
+ database or filesystem files). It also supports an additional mechanism to
+ map URLs to code (:term:`URL dispatch`). However, for the purposes of this
+ tutorial, we'll only be using :term:`traversal` and :term:`ZODB`.
+
+.. _pyramid_chameleon:
+ http://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/
+
+.. _pyramid_tm:
+ http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/
+
+.. _pyramid_zodbconn:
+ http://docs.pylonsproject.org/projects/pyramid-zodbconn/en/latest/
+
+.. _transaction:
+ http://zodb.readthedocs.org/en/latest/transactions.html
diff --git a/docs/tutorials/wiki/src/authorization/CHANGES.txt b/docs/tutorials/wiki/src/authorization/CHANGES.txt
index e14f633ab..35a34f332 100644
--- a/docs/tutorials/wiki/src/authorization/CHANGES.txt
+++ b/docs/tutorials/wiki/src/authorization/CHANGES.txt
@@ -1,5 +1,4 @@
0.0
---
-- Initial version
-
+- Initial version
diff --git a/docs/tutorials/wiki/src/authorization/README.txt b/docs/tutorials/wiki/src/authorization/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/authorization/README.txt
+++ b/docs/tutorials/wiki/src/authorization/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini
index 72bd22e54..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/authorization/development.ini
+++ b/docs/tutorials/wiki/src/authorization/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,12 +29,12 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -62,4 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini
index d9bf27c42..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/authorization/production.ini
+++ b/docs/tutorials/wiki/src/authorization/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,7 +29,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py
index e2e96379d..beeed75c9 100644
--- a/docs/tutorials/wiki/src/authorization/setup.py
+++ b/docs/tutorials/wiki/src/authorization/setup.py
@@ -20,16 +20,22 @@ requires = [
'docutils',
]
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='tutorial',
version='0.0',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -37,9 +43,10 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
- tests_require=requires,
- test_suite="tutorial",
entry_points="""\
[paste.app_factory]
main = tutorial:main
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models.py
index 582ff0d7e..38fdd2dfc 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/models.py
@@ -17,7 +17,7 @@ class Page(Persistent):
self.data = data
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = Wiki()
frontpage = Page('This is the front page')
app_root['FrontPage'] = frontpage
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.min.css b/docs/tutorials/wiki/src/authorization/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki/src/authorization/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
index 1b30f42b6..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt
@@ -34,14 +34,15 @@
<div class="col-md-10">
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
index 0b9046d47..40f3c47af 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py
@@ -2,122 +2,16 @@ import unittest
from pyramid import testing
-class PageModelTests(unittest.TestCase):
- def _getTargetClass(self):
- from .models import Page
- return Page
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
- def _makeOne(self, data=u'some data'):
- return self._getTargetClass()(data=data)
+ def tearDown(self):
+ testing.tearDown()
- def test_constructor(self):
- instance = self._makeOne()
- self.assertEqual(instance.data, u'some data')
-
-class WikiModelTests(unittest.TestCase):
-
- def _getTargetClass(self):
- from .models import Wiki
- return Wiki
-
- def _makeOne(self):
- return self._getTargetClass()()
-
- def test_it(self):
- wiki = self._makeOne()
- self.assertEqual(wiki.__parent__, None)
- self.assertEqual(wiki.__name__, None)
-
-class AppmakerTests(unittest.TestCase):
-
- def _callFUT(self, zodb_root):
- from .models import appmaker
- return appmaker(zodb_root)
-
- def test_it(self):
- root = {}
- self._callFUT(root)
- self.assertEqual(root['app_root']['FrontPage'].data,
- 'This is the front page')
-
-class ViewWikiTests(unittest.TestCase):
- def test_it(self):
- from .views import view_wiki
- context = testing.DummyResource()
- request = testing.DummyRequest()
- response = view_wiki(context, request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
-
-class ViewPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from .views import view_page
- return view_page(context, request)
-
- def test_it(self):
- wiki = testing.DummyResource()
- wiki['IDoExist'] = testing.DummyResource()
- context = testing.DummyResource(data='Hello CruelWorld IDoExist')
- context.__parent__ = wiki
- context.__name__ = 'thepage'
- request = testing.DummyRequest()
- info = self._callFUT(context, request)
- self.assertEqual(info['page'], context)
- self.assertEqual(
- info['content'],
- '<div class="document">\n'
- '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
- 'CruelWorld</a> '
- '<a href="http://example.com/IDoExist/">'
- 'IDoExist</a>'
- '</p>\n</div>\n')
- self.assertEqual(info['edit_url'],
- 'http://example.com/thepage/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from .views import add_page
- return add_page(context, request)
-
- def test_it_notsubmitted(self):
- context = testing.DummyResource()
+ def test_my_view(self):
+ from .views import my_view
request = testing.DummyRequest()
- request.subpath = ['AnotherPage']
- info = self._callFUT(context, request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(
- info['save_url'],
- request.resource_url(context, 'add_page', 'AnotherPage'))
-
- def test_it_submitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.subpath = ['AnotherPage']
- self._callFUT(context, request)
- page = context['AnotherPage']
- self.assertEqual(page.data, 'Hello yo!')
- self.assertEqual(page.__name__, 'AnotherPage')
- self.assertEqual(page.__parent__, context)
-
-class EditPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from .views import edit_page
- return edit_page(context, request)
-
- def test_it_notsubmitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest()
- info = self._callFUT(context, request)
- self.assertEqual(info['page'], context)
- self.assertEqual(info['save_url'],
- request.resource_url(context, 'edit_page'))
-
- def test_it_submitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- response = self._callFUT(context, request)
- self.assertEqual(response.location, 'http://example.com/')
- self.assertEqual(context.data, 'Hello yo!')
+ info = my_view(request)
+ self.assertEqual(info['project'], 'tutorial')
diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py
index 62e96e0e7..c271d2cc1 100644
--- a/docs/tutorials/wiki/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py
@@ -37,15 +37,15 @@ def view_page(context, request):
view_url = request.resource_url(page)
return '<a href="%s">%s</a>' % (view_url, word)
else:
- add_url = request.application_url + '/add_page/' + word
+ add_url = request.application_url + '/add_page/' + word
return '<a href="%s">%s</a>' % (add_url, word)
content = publish_parts(context.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page = context, content = content, edit_url = edit_url,
- logged_in = request.authenticated_userid)
+ return dict(page=context, content=content, edit_url=edit_url,
+ logged_in=request.authenticated_userid)
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
@@ -58,7 +58,7 @@ def add_page(context, request):
page.__name__ = pagename
page.__parent__ = context
context[pagename] = page
- return HTTPFound(location = request.resource_url(page))
+ return HTTPFound(location=request.resource_url(page))
save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
page.__name__ = pagename
@@ -73,7 +73,7 @@ def add_page(context, request):
def edit_page(context, request):
if 'form.submitted' in request.params:
context.data = request.params['body']
- return HTTPFound(location = request.resource_url(context))
+ return HTTPFound(location=request.resource_url(context))
return dict(page=context,
save_url=request.resource_url(context, 'edit_page'),
@@ -86,7 +86,7 @@ def login(request):
login_url = request.resource_url(request.context, 'login')
referrer = request.url
if referrer == login_url:
- referrer = '/' # never use the login form itself as came_from
+ referrer = '/' # never use the login form itself as came_from
came_from = request.params.get('came_from', referrer)
message = ''
login = ''
@@ -96,20 +96,21 @@ def login(request):
password = request.params['password']
if USERS.get(login) == password:
headers = remember(request, login)
- return HTTPFound(location = came_from,
- headers = headers)
+ return HTTPFound(location=came_from,
+ headers=headers)
message = 'Failed login'
return dict(
- message = message,
- url = request.application_url + '/login',
- came_from = came_from,
- login = login,
- password = password,
- )
+ message=message,
+ url=request.application_url + '/login',
+ came_from=came_from,
+ login=login,
+ password=password,
+ )
+
@view_config(context='.models.Wiki', name='logout')
def logout(request):
headers = forget(request)
- return HTTPFound(location = request.resource_url(request.context),
- headers = headers)
+ return HTTPFound(location=request.resource_url(request.context),
+ headers=headers)
diff --git a/docs/tutorials/wiki/src/basiclayout/README.txt b/docs/tutorials/wiki/src/basiclayout/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki/src/basiclayout/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini
index 72bd22e54..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/basiclayout/development.ini
+++ b/docs/tutorials/wiki/src/basiclayout/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,12 +29,12 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -62,4 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini
index d9bf27c42..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/basiclayout/production.ini
+++ b/docs/tutorials/wiki/src/basiclayout/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,7 +29,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py
index 58a454f80..46b395568 100644
--- a/docs/tutorials/wiki/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki/src/basiclayout/setup.py
@@ -19,16 +19,22 @@ requires = [
'waitress',
]
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='tutorial',
version='0.0',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -36,9 +42,10 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
- tests_require=requires,
- test_suite="tutorial",
entry_points="""\
[paste.app_factory]
main = tutorial:main
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
index a94b36ef4..e5aa3e9f7 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/models.py
@@ -6,7 +6,7 @@ class MyModel(PersistentMapping):
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = MyModel()
zodb_root['app_root'] = app_root
import transaction
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.min.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
index 1b30f42b6..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt
@@ -34,14 +34,15 @@
<div class="col-md-10">
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
index 7f6523c66..40f3c47af 100644
--- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py
@@ -2,6 +2,7 @@ import unittest
from pyramid import testing
+
class ViewTests(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
diff --git a/docs/tutorials/wiki/src/installation/CHANGES.txt b/docs/tutorials/wiki/src/installation/CHANGES.txt
new file mode 100644
index 000000000..35a34f332
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version
diff --git a/docs/tutorials/wiki/src/installation/MANIFEST.in b/docs/tutorials/wiki/src/installation/MANIFEST.in
new file mode 100644
index 000000000..81beba1b1
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.txt *.ini *.cfg *.rst
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki/src/installation/README.txt b/docs/tutorials/wiki/src/installation/README.txt
new file mode 100644
index 000000000..dcb3605b8
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/README.txt
@@ -0,0 +1,12 @@
+tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki/src/installation/development.ini b/docs/tutorials/wiki/src/installation/development.ini
new file mode 100644
index 000000000..6bf4b198e
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/development.ini
@@ -0,0 +1,65 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_zodbconn
+ pyramid_tm
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+host = 127.0.0.1
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/installation/production.ini b/docs/tutorials/wiki/src/installation/production.ini
new file mode 100644
index 000000000..4e9892e7b
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/production.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_tm
+ pyramid_zodbconn
+
+tm.attempts = 3
+zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+host = 0.0.0.0
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_tutorial]
+level = WARN
+handlers =
+qualname = tutorial
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/installation/setup.py b/docs/tutorials/wiki/src/installation/setup.py
new file mode 100644
index 000000000..46b395568
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/setup.py
@@ -0,0 +1,53 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'pyramid',
+ 'pyramid_chameleon',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'pyramid_zodbconn',
+ 'transaction',
+ 'ZODB3',
+ 'waitress',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
+setup(name='tutorial',
+ version='0.0',
+ description='tutorial',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web pylons pyramid',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ """,
+ )
diff --git a/docs/tutorials/wiki/src/installation/tutorial/__init__.py b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
new file mode 100644
index 000000000..f2a86df47
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/__init__.py
@@ -0,0 +1,18 @@
+from pyramid.config import Configurator
+from pyramid_zodbconn import get_connection
+from .models import appmaker
+
+
+def root_factory(request):
+ conn = get_connection(request)
+ return appmaker(conn.root())
+
+
+def main(global_config, **settings):
+ """ This function returns a Pyramid WSGI application.
+ """
+ config = Configurator(root_factory=root_factory, settings=settings)
+ config.include('pyramid_chameleon')
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/installation/tutorial/models.py b/docs/tutorials/wiki/src/installation/tutorial/models.py
new file mode 100644
index 000000000..e5aa3e9f7
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/models.py
@@ -0,0 +1,14 @@
+from persistent.mapping import PersistentMapping
+
+
+class MyModel(PersistentMapping):
+ __parent__ = __name__ = None
+
+
+def appmaker(zodb_root):
+ if 'app_root' not in zodb_root:
+ app_root = MyModel()
+ zodb_root['app_root'] = app_root
+ import transaction
+ transaction.commit()
+ return zodb_root['app_root']
diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki/src/installation/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/installation/tutorial/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki/src/installation/tutorial/static/theme.css b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
index c9b0cec21..f8cbe2e2c 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/installation/tutorial/templates/mytemplate.pt
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
- <title>Alchemy Scaffold for The Pyramid Web Framework</title>
+ <title>ZODB Scaffold for The Pyramid Web Framework</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
@@ -33,15 +33,16 @@
</div>
<div class="col-md-10">
<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki/src/installation/tutorial/tests.py b/docs/tutorials/wiki/src/installation/tutorial/tests.py
new file mode 100644
index 000000000..40f3c47af
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/tests.py
@@ -0,0 +1,17 @@
+import unittest
+
+from pyramid import testing
+
+
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def test_my_view(self):
+ from .views import my_view
+ request = testing.DummyRequest()
+ info = my_view(request)
+ self.assertEqual(info['project'], 'tutorial')
diff --git a/docs/tutorials/wiki/src/installation/tutorial/views.py b/docs/tutorials/wiki/src/installation/tutorial/views.py
new file mode 100644
index 000000000..628ce15ed
--- /dev/null
+++ b/docs/tutorials/wiki/src/installation/tutorial/views.py
@@ -0,0 +1,7 @@
+from pyramid.view import view_config
+from .models import MyModel
+
+
+@view_config(context=MyModel, renderer='templates/mytemplate.pt')
+def my_view(request):
+ return {'project': 'tutorial'}
diff --git a/docs/tutorials/wiki/src/models/CHANGES.txt b/docs/tutorials/wiki/src/models/CHANGES.txt
index ffa255da8..35a34f332 100644
--- a/docs/tutorials/wiki/src/models/CHANGES.txt
+++ b/docs/tutorials/wiki/src/models/CHANGES.txt
@@ -1,4 +1,4 @@
0.0
---
-- Initial version
+- Initial version
diff --git a/docs/tutorials/wiki/src/models/README.txt b/docs/tutorials/wiki/src/models/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/models/README.txt
+++ b/docs/tutorials/wiki/src/models/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini
index 72bd22e54..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/models/development.ini
+++ b/docs/tutorials/wiki/src/models/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,12 +29,12 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -62,4 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini
index d9bf27c42..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/models/production.ini
+++ b/docs/tutorials/wiki/src/models/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,7 +29,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py
index 58a454f80..46b395568 100644
--- a/docs/tutorials/wiki/src/models/setup.py
+++ b/docs/tutorials/wiki/src/models/setup.py
@@ -19,16 +19,22 @@ requires = [
'waitress',
]
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='tutorial',
version='0.0',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -36,9 +42,10 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
- tests_require=requires,
- test_suite="tutorial",
entry_points="""\
[paste.app_factory]
main = tutorial:main
diff --git a/docs/tutorials/wiki/src/models/tutorial/models.py b/docs/tutorials/wiki/src/models/tutorial/models.py
index 9761856c6..aa907aee5 100644
--- a/docs/tutorials/wiki/src/models/tutorial/models.py
+++ b/docs/tutorials/wiki/src/models/tutorial/models.py
@@ -10,7 +10,7 @@ class Page(Persistent):
self.data = data
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = Wiki()
frontpage = Page('This is the front page')
app_root['FrontPage'] = frontpage
diff --git a/docs/tutorials/wiki/src/models/tutorial/static/theme.min.css b/docs/tutorials/wiki/src/models/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki/src/models/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
index 1b30f42b6..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt
@@ -34,14 +34,15 @@
<div class="col-md-10">
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py
index 0c5f99575..40f3c47af 100644
--- a/docs/tutorials/wiki/src/models/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/models/tutorial/tests.py
@@ -2,50 +2,6 @@ import unittest
from pyramid import testing
-class PageModelTests(unittest.TestCase):
-
- def _getTargetClass(self):
- from .models import Page
- return Page
-
- def _makeOne(self, data=u'some data'):
- return self._getTargetClass()(data=data)
-
- def test_constructor(self):
- instance = self._makeOne()
- self.assertEqual(instance.data, u'some data')
-
-class WikiModelTests(unittest.TestCase):
-
- def _getTargetClass(self):
- from .models import Wiki
- return Wiki
-
- def _makeOne(self):
- return self._getTargetClass()()
-
- def test_it(self):
- wiki = self._makeOne()
- self.assertEqual(wiki.__parent__, None)
- self.assertEqual(wiki.__name__, None)
-
-class AppmakerTests(unittest.TestCase):
-
- def _callFUT(self, zodb_root):
- from .models import appmaker
- return appmaker(zodb_root)
-
- def test_no_app_root(self):
- root = {}
- self._callFUT(root)
- self.assertEqual(root['app_root']['FrontPage'].data,
- 'This is the front page')
-
- def test_w_app_root(self):
- app_root = object()
- root = {'app_root': app_root}
- self._callFUT(root)
- self.assertTrue(root['app_root'] is app_root)
class ViewTests(unittest.TestCase):
def setUp(self):
diff --git a/docs/tutorials/wiki/src/tests/CHANGES.txt b/docs/tutorials/wiki/src/tests/CHANGES.txt
index e14f633ab..35a34f332 100644
--- a/docs/tutorials/wiki/src/tests/CHANGES.txt
+++ b/docs/tutorials/wiki/src/tests/CHANGES.txt
@@ -1,5 +1,4 @@
0.0
---
-- Initial version
-
+- Initial version
diff --git a/docs/tutorials/wiki/src/tests/README.txt b/docs/tutorials/wiki/src/tests/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/tests/README.txt
+++ b/docs/tutorials/wiki/src/tests/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/tests/development.ini b/docs/tutorials/wiki/src/tests/development.ini
index 72bd22e54..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/tests/development.ini
+++ b/docs/tutorials/wiki/src/tests/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,12 +29,12 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -62,4 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/tests/production.ini b/docs/tutorials/wiki/src/tests/production.ini
index d9bf27c42..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/tests/production.ini
+++ b/docs/tutorials/wiki/src/tests/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,7 +29,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/tests/setup.py b/docs/tutorials/wiki/src/tests/setup.py
index b67b702cf..beeed75c9 100644
--- a/docs/tutorials/wiki/src/tests/setup.py
+++ b/docs/tutorials/wiki/src/tests/setup.py
@@ -18,7 +18,12 @@ requires = [
'ZODB3',
'waitress',
'docutils',
- 'WebTest', # add this
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
]
setup(name='tutorial',
@@ -26,11 +31,11 @@ setup(name='tutorial',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -38,9 +43,10 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
- tests_require=requires,
- test_suite="tutorial",
entry_points="""\
[paste.app_factory]
main = tutorial:main
diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
index bd3c5619f..39b94abd1 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py
@@ -19,9 +19,9 @@ def main(global_config, **settings):
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(root_factory=root_factory, settings=settings)
- config.include('pyramid_chameleon')
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
+ config.include('pyramid_chameleon')
config.add_static_view('static', 'static', cache_max_age=3600)
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models.py
index 582ff0d7e..38fdd2dfc 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/models.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/models.py
@@ -17,7 +17,7 @@ class Page(Persistent):
self.data = data
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = Wiki()
frontpage = Page('This is the front page')
app_root['FrontPage'] = frontpage
diff --git a/docs/tutorials/wiki/src/tests/tutorial/static/theme.min.css b/docs/tutorials/wiki/src/tests/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki/src/tests/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
index 1b30f42b6..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt
@@ -34,14 +34,15 @@
<div class="col-md-10">
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py
index 5add04c20..04beaea44 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py
@@ -164,6 +164,10 @@ class FunctionalTests(unittest.TestCase):
res = self.testapp.get('/SomePage', status=404)
self.assertTrue(b'Not Found' in res.body)
+ def test_referrer_is_login(self):
+ res = self.testapp.get('/login', status=200)
+ self.assertTrue(b'name="came_from" value="/"' in res.body)
+
def test_successful_log_in(self):
res = self.testapp.get( self.viewer_login, status=302)
self.assertEqual(res.location, 'http://localhost/FrontPage')
diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py
index 62e96e0e7..c271d2cc1 100644
--- a/docs/tutorials/wiki/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki/src/tests/tutorial/views.py
@@ -37,15 +37,15 @@ def view_page(context, request):
view_url = request.resource_url(page)
return '<a href="%s">%s</a>' % (view_url, word)
else:
- add_url = request.application_url + '/add_page/' + word
+ add_url = request.application_url + '/add_page/' + word
return '<a href="%s">%s</a>' % (add_url, word)
content = publish_parts(context.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
edit_url = request.resource_url(context, 'edit_page')
- return dict(page = context, content = content, edit_url = edit_url,
- logged_in = request.authenticated_userid)
+ return dict(page=context, content=content, edit_url=edit_url,
+ logged_in=request.authenticated_userid)
@view_config(name='add_page', context='.models.Wiki',
renderer='templates/edit.pt',
@@ -58,7 +58,7 @@ def add_page(context, request):
page.__name__ = pagename
page.__parent__ = context
context[pagename] = page
- return HTTPFound(location = request.resource_url(page))
+ return HTTPFound(location=request.resource_url(page))
save_url = request.resource_url(context, 'add_page', pagename)
page = Page('')
page.__name__ = pagename
@@ -73,7 +73,7 @@ def add_page(context, request):
def edit_page(context, request):
if 'form.submitted' in request.params:
context.data = request.params['body']
- return HTTPFound(location = request.resource_url(context))
+ return HTTPFound(location=request.resource_url(context))
return dict(page=context,
save_url=request.resource_url(context, 'edit_page'),
@@ -86,7 +86,7 @@ def login(request):
login_url = request.resource_url(request.context, 'login')
referrer = request.url
if referrer == login_url:
- referrer = '/' # never use the login form itself as came_from
+ referrer = '/' # never use the login form itself as came_from
came_from = request.params.get('came_from', referrer)
message = ''
login = ''
@@ -96,20 +96,21 @@ def login(request):
password = request.params['password']
if USERS.get(login) == password:
headers = remember(request, login)
- return HTTPFound(location = came_from,
- headers = headers)
+ return HTTPFound(location=came_from,
+ headers=headers)
message = 'Failed login'
return dict(
- message = message,
- url = request.application_url + '/login',
- came_from = came_from,
- login = login,
- password = password,
- )
+ message=message,
+ url=request.application_url + '/login',
+ came_from=came_from,
+ login=login,
+ password=password,
+ )
+
@view_config(context='.models.Wiki', name='logout')
def logout(request):
headers = forget(request)
- return HTTPFound(location = request.resource_url(request.context),
- headers = headers)
+ return HTTPFound(location=request.resource_url(request.context),
+ headers=headers)
diff --git a/docs/tutorials/wiki/src/views/CHANGES.txt b/docs/tutorials/wiki/src/views/CHANGES.txt
index 1544cf53b..35a34f332 100644
--- a/docs/tutorials/wiki/src/views/CHANGES.txt
+++ b/docs/tutorials/wiki/src/views/CHANGES.txt
@@ -1,3 +1,4 @@
-0.1
+0.0
+---
- Initial version
+- Initial version
diff --git a/docs/tutorials/wiki/src/views/README.txt b/docs/tutorials/wiki/src/views/README.txt
index d41f7f90f..dcb3605b8 100644
--- a/docs/tutorials/wiki/src/views/README.txt
+++ b/docs/tutorials/wiki/src/views/README.txt
@@ -1,4 +1,12 @@
tutorial README
+==================
+Getting Started
+---------------
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/pserve development.ini
diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini
index 72bd22e54..6bf4b198e 100644
--- a/docs/tutorials/wiki/src/views/development.ini
+++ b/docs/tutorials/wiki/src/views/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,12 +29,12 @@ zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -62,4 +62,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini
index d9bf27c42..4e9892e7b 100644
--- a/docs/tutorials/wiki/src/views/production.ini
+++ b/docs/tutorials/wiki/src/views/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -29,7 +29,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -57,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py
index e2e96379d..beeed75c9 100644
--- a/docs/tutorials/wiki/src/views/setup.py
+++ b/docs/tutorials/wiki/src/views/setup.py
@@ -20,16 +20,22 @@ requires = [
'docutils',
]
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='tutorial',
version='0.0',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -37,9 +43,10 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
- tests_require=requires,
- test_suite="tutorial",
entry_points="""\
[paste.app_factory]
main = tutorial:main
diff --git a/docs/tutorials/wiki/src/views/tutorial/models.py b/docs/tutorials/wiki/src/views/tutorial/models.py
index 9761856c6..aa907aee5 100644
--- a/docs/tutorials/wiki/src/views/tutorial/models.py
+++ b/docs/tutorials/wiki/src/views/tutorial/models.py
@@ -10,7 +10,7 @@ class Page(Persistent):
self.data = data
def appmaker(zodb_root):
- if not 'app_root' in zodb_root:
+ if 'app_root' not in zodb_root:
app_root = Wiki()
frontpage = Page('This is the front page')
app_root['FrontPage'] = frontpage
diff --git a/docs/tutorials/wiki/src/views/tutorial/static/theme.min.css b/docs/tutorials/wiki/src/views/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki/src/views/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
index 1b30f42b6..f8cbe2e2c 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt
@@ -34,14 +34,15 @@
<div class="col-md-10">
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">ZODB scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
+ <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
index e7b0dc23e..93580658b 100644
--- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt
@@ -8,7 +8,7 @@
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
- <title>${page.name} - Pyramid tutorial wiki (based on
+ <title>${page.__name__} - Pyramid tutorial wiki (based on
TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
diff --git a/docs/tutorials/wiki/src/views/tutorial/tests.py b/docs/tutorials/wiki/src/views/tutorial/tests.py
index 663c9f405..40f3c47af 100644
--- a/docs/tutorials/wiki/src/views/tutorial/tests.py
+++ b/docs/tutorials/wiki/src/views/tutorial/tests.py
@@ -2,125 +2,16 @@ import unittest
from pyramid import testing
-class PageModelTests(unittest.TestCase):
- def _getTargetClass(self):
- from .models import Page
- return Page
+class ViewTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
- def _makeOne(self, data=u'some data'):
- return self._getTargetClass()(data=data)
+ def tearDown(self):
+ testing.tearDown()
- def test_constructor(self):
- instance = self._makeOne()
- self.assertEqual(instance.data, u'some data')
-
-class WikiModelTests(unittest.TestCase):
-
- def _getTargetClass(self):
- from .models import Wiki
- return Wiki
-
- def _makeOne(self):
- return self._getTargetClass()()
-
- def test_it(self):
- wiki = self._makeOne()
- self.assertEqual(wiki.__parent__, None)
- self.assertEqual(wiki.__name__, None)
-
-class AppmakerTests(unittest.TestCase):
-
- def _callFUT(self, zodb_root):
- from .models import appmaker
- return appmaker(zodb_root)
-
- def test_it(self):
- root = {}
- self._callFUT(root)
- self.assertEqual(root['app_root']['FrontPage'].data,
- 'This is the front page')
-
-class ViewWikiTests(unittest.TestCase):
- def test_it(self):
- from .views import view_wiki
- context = testing.DummyResource()
- request = testing.DummyRequest()
- response = view_wiki(context, request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
-
-class ViewPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from .views import view_page
- return view_page(context, request)
-
- def test_it(self):
- wiki = testing.DummyResource()
- wiki['IDoExist'] = testing.DummyResource()
- context = testing.DummyResource(data='Hello CruelWorld IDoExist')
- context.__parent__ = wiki
- context.__name__ = 'thepage'
- request = testing.DummyRequest()
- info = self._callFUT(context, request)
- self.assertEqual(info['page'], context)
- self.assertEqual(
- info['content'],
- '<div class="document">\n'
- '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
- 'CruelWorld</a> '
- '<a href="http://example.com/IDoExist/">'
- 'IDoExist</a>'
- '</p>\n</div>\n')
- self.assertEqual(info['edit_url'],
- 'http://example.com/thepage/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from .views import add_page
- return add_page(context, request)
-
- def test_it_notsubmitted(self):
- context = testing.DummyResource()
+ def test_my_view(self):
+ from .views import my_view
request = testing.DummyRequest()
- request.subpath = ['AnotherPage']
- info = self._callFUT(context, request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(
- info['save_url'],
- request.resource_url(context, 'add_page', 'AnotherPage'))
-
- def test_it_submitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.subpath = ['AnotherPage']
- self._callFUT(context, request)
- page = context['AnotherPage']
- self.assertEqual(page.data, 'Hello yo!')
- self.assertEqual(page.__name__, 'AnotherPage')
- self.assertEqual(page.__parent__, context)
-
-class EditPageTests(unittest.TestCase):
- def _callFUT(self, context, request):
- from .views import edit_page
- return edit_page(context, request)
-
- def test_it_notsubmitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest()
- info = self._callFUT(context, request)
- self.assertEqual(info['page'], context)
- self.assertEqual(info['save_url'],
- request.resource_url(context, 'edit_page'))
-
- def test_it_submitted(self):
- context = testing.DummyResource()
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- response = self._callFUT(context, request)
- self.assertEqual(response.location, 'http://example.com/')
- self.assertEqual(context.data, 'Hello yo!')
-
-
-
+ info = my_view(request)
+ self.assertEqual(info['project'], 'tutorial')
diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst
index e255812fc..85a023cc9 100644
--- a/docs/tutorials/wiki/tests.rst
+++ b/docs/tutorials/wiki/tests.rst
@@ -1,3 +1,5 @@
+.. _wiki_adding_tests:
+
============
Adding Tests
============
@@ -49,55 +51,26 @@ follows:
Running the tests
=================
-We can run these tests by using ``setup.py test`` in the same way we did in
-:ref:`running_tests`. However, first we must edit our ``setup.py`` to
-include a dependency on WebTest, which we've used in our ``tests.py``.
-Change the ``requires`` list in ``setup.py`` to include ``WebTest``.
-
-.. literalinclude:: src/tests/setup.py
- :linenos:
- :language: python
- :lines: 11-22
- :emphasize-lines: 11
-
-After we've added a dependency on WebTest in ``setup.py``, we need to run
-``setup.py develop`` to get WebTest installed into our virtualenv. Assuming
-our shell's current working directory is the "tutorial" distribution
-directory:
-
-On UNIX:
-
-.. code-block:: text
-
- $ $VENV/bin/python setup.py develop
-
-On Windows:
-
-.. code-block:: text
-
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
-
-Once that command has completed successfully, we can run the tests
-themselves:
+We can run these tests by using ``py.test`` similarly to how we did in
+:ref:`running_tests`. Courtesy of the scaffold, our testing dependencies have
+already been satisfied and ``py.test`` and coverage have already been
+configured, so we can jump right to running tests.
On UNIX:
.. code-block:: text
- $ $VENV/bin/python setup.py test -q
+ $ $VENV/bin/py.test -q
On Windows:
.. code-block:: text
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py test -q
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q
The expected result should look like the following:
.. code-block:: text
- .........
- ----------------------------------------------------------------------
- Ran 23 tests in 1.653s
-
- OK
+ ........................
+ 24 passed in 2.46 seconds
diff --git a/docs/tutorials/wiki2/authentication.rst b/docs/tutorials/wiki2/authentication.rst
new file mode 100644
index 000000000..5447db861
--- /dev/null
+++ b/docs/tutorials/wiki2/authentication.rst
@@ -0,0 +1,312 @@
+.. _wiki2_adding_authentication:
+
+=====================
+Adding authentication
+=====================
+
+:app:`Pyramid` provides facilities for :term:`authentication` and
+:term:`authorization`. In this section we'll focus solely on the authentication
+APIs to add login and logout functionality to our wiki.
+
+We will implement authentication with the following steps:
+
+* Add an :term:`authentication policy` and a ``request.user`` computed property
+ (``security.py``).
+* Add routes for ``/login`` and ``/logout`` (``routes.py``).
+* Add login and logout views (``views/auth.py``).
+* Add a login template (``login.jinja2``).
+* Add "Login" and "Logout" links to every page based on the user's
+ authenticated state (``layout.jinja2``).
+* Make the existing views verify user state (``views/default.py``).
+* Redirect to ``/login`` when a user is denied access to any of the views that
+ require permission, instead of a default "403 Forbidden" page
+ (``views/auth.py``).
+
+
+Authenticating requests
+-----------------------
+
+The core of :app:`Pyramid` authentication is an :term:`authentication policy`
+which is used to identify authentication information from a ``request``,
+as well as handling the low-level login and logout operations required to
+track users across requests (via cookies, headers, or whatever else you can
+imagine).
+
+
+Add the authentication policy
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Create a new file ``tutorial/security.py`` with the following content:
+
+.. literalinclude:: src/authentication/tutorial/security.py
+ :linenos:
+ :language: python
+
+Here we've defined:
+
+* A new authentication policy named ``MyAuthenticationPolicy``, which is
+ subclassed from Pyramid's
+ :class:`pyramid.authentication.AuthTktAuthenticationPolicy`, which tracks the
+ :term:`userid` using a signed cookie (lines 7-11).
+* A ``get_user`` function, which can convert the ``unauthenticated_userid``
+ from the policy into a ``User`` object from our database (lines 13-17).
+* The ``get_user`` is registered on the request as ``request.user`` to be used
+ throughout our application as the authenticated ``User`` object for the
+ logged-in user (line 27).
+
+The logic in this file is a little bit interesting, so we'll go into detail
+about what's happening here:
+
+First, the default authentication policies all provide a method named
+``unauthenticated_userid`` which is responsible for the low-level parsing
+of the information in the request (cookies, headers, etc.). If a ``userid``
+is found, then it is returned from this method. This is named
+``unauthenticated_userid`` because, at the lowest level, it knows the value of
+the userid in the cookie, but it doesn't know if it's actually a user in our
+system (remember, anything the user sends to our app is untrusted).
+
+Second, our application should only care about ``authenticated_userid`` and
+``request.user``, which have gone through our application-specific process of
+validating that the user is logged in.
+
+In order to provide an ``authenticated_userid`` we need a verification step.
+That can happen anywhere, so we've elected to do it inside of the cached
+``request.user`` computed property. This is a convenience that makes
+``request.user`` the source of truth in our system. It is either ``None`` or
+a ``User`` object from our database. This is why the ``get_user`` function
+uses the ``unauthenticated_userid`` to check the database.
+
+
+Configure the app
+~~~~~~~~~~~~~~~~~
+
+Since we've added a new ``tutorial/security.py`` module, we need to include it.
+Open the file ``tutorial/__init__.py`` and edit the following lines:
+
+.. literalinclude:: src/authentication/tutorial/__init__.py
+ :linenos:
+ :emphasize-lines: 11
+ :language: python
+
+Our authentication policy is expecting a new setting, ``auth.secret``. Open
+the file ``development.ini`` and add the highlighted line below:
+
+.. literalinclude:: src/authentication/development.ini
+ :lines: 18-20
+ :emphasize-lines: 3
+ :lineno-match:
+ :language: ini
+
+Finally, best practices tell us to use a different secret for production, so
+open ``production.ini`` and add a different secret:
+
+.. literalinclude:: src/authentication/production.ini
+ :lines: 15-17
+ :emphasize-lines: 3
+ :lineno-match:
+ :language: ini
+
+
+Add permission checks
+~~~~~~~~~~~~~~~~~~~~~
+
+:app:`Pyramid` has full support for declarative authorization, which we'll
+cover in the next chapter. However, many people looking to get their feet wet
+are just interested in authentication with some basic form of home-grown
+authorization. We'll show below how to accomplish the simple security goals of
+our wiki, now that we can track the logged-in state of users.
+
+Remember our goals:
+
+* Allow only ``editor`` and ``basic`` logged-in users to create new pages.
+* Only allow ``editor`` users and the page creator (possibly a ``basic`` user)
+ to edit pages.
+
+Open the file ``tutorial/views/default.py`` and fix the following imports:
+
+.. literalinclude:: src/authentication/tutorial/views/default.py
+ :lines: 5-13
+ :lineno-match:
+ :emphasize-lines: 2,9
+ :language: python
+
+Change the two highlighted lines.
+
+In the same file, now edit the ``edit_page`` view function:
+
+.. literalinclude:: src/authentication/tutorial/views/default.py
+ :lines: 45-60
+ :lineno-match:
+ :emphasize-lines: 5-7
+ :language: python
+
+Only the highlighted lines need to be changed.
+
+If the user either is not logged in or the user is not the page's creator
+*and* not an ``editor``, then we raise ``HTTPForbidden``.
+
+In the same file, now edit the ``add_page`` view function:
+
+.. literalinclude:: src/authentication/tutorial/views/default.py
+ :lines: 62-76
+ :lineno-match:
+ :emphasize-lines: 3-5,13
+ :language: python
+
+Only the highlighted lines need to be changed.
+
+If the user either is not logged in or is not in the ``basic`` or ``editor``
+roles, then we raise ``HTTPForbidden``, which will return a "403 Forbidden"
+response to the user. However, we will hook this later to redirect to the login
+page. Also, now that we have ``request.user``, we no longer have to hard-code
+the creator as the ``editor`` user, so we can finally drop that hack.
+
+These simple checks should protect our views.
+
+
+Login, logout
+-------------
+
+Now that we've got the ability to detect logged-in users, we need to add the
+``/login`` and ``/logout`` views so that they can actually login and logout!
+
+
+Add routes for ``/login`` and ``/logout``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Go back to ``tutorial/routes.py`` and add these two routes as highlighted:
+
+.. literalinclude:: src/authentication/tutorial/routes.py
+ :lines: 3-6
+ :lineno-match:
+ :emphasize-lines: 2-3
+ :language: python
+
+.. note:: The preceding lines must be added *before* the following
+ ``view_page`` route definition:
+
+ .. literalinclude:: src/authentication/tutorial/routes.py
+ :lines: 6
+ :lineno-match:
+ :language: python
+
+ This is because ``view_page``'s route definition uses a catch-all
+ "replacement marker" ``/{pagename}`` (see :ref:`route_pattern_syntax`),
+ which will catch any route that was not already caught by any route
+ registered before it. Hence, for ``login`` and ``logout`` views to
+ have the opportunity of being matched (or "caught"), they must be above
+ ``/{pagename}``.
+
+
+Add login, logout, and forbidden views
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Create a new file ``tutorial/views/auth.py``, and add the following code to it:
+
+.. literalinclude:: src/authentication/tutorial/views/auth.py
+ :linenos:
+ :language: python
+
+This code adds three new views to the application:
+
+- The ``login`` view renders a login form and processes the post from the
+ login form, checking credentials against our ``users`` table in the database.
+
+ The check is done by first finding a ``User`` record in the database, then
+ using our ``user.check_password`` method to compare the hashed passwords.
+
+ If the credentials are valid, then we use our authentication policy to store
+ the user's id in the response using :meth:`pyramid.security.remember`.
+
+ Finally, the user is redirected back to either the page which they were
+ trying to access (``next``) or the front page as a fallback. This parameter
+ is used by our forbidden view, as explained below, to finish the login
+ workflow.
+
+- The ``logout`` view handles requests to ``/logout`` by clearing the
+ credentials using :meth:`pyramid.security.forget`, then redirecting them to
+ the front page.
+
+- The ``forbidden_view`` is registered using the
+ :class:`pyramid.view.forbidden_view_config` decorator. This is a special
+ :term:`exception view`, which is invoked when a
+ :class:`pyramid.httpexceptions.HTTPForbidden` exception is raised.
+
+ This view will handle a forbidden error by redirecting the user to
+ ``/login``. As a convenience, it also sets the ``next=`` query string to the
+ current URL (the one that is forbidding access). This way, if the user
+ successfully logs in, they will be sent back to the page which they had been
+ trying to access.
+
+
+Add the ``login.jinja2`` template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Create ``tutorial/templates/login.jinja2`` with the following content:
+
+.. literalinclude:: src/authentication/tutorial/templates/login.jinja2
+ :language: html
+
+The above template is referenced in the login view that we just added in
+``tutorial/views/auth.py``.
+
+
+Add "Login" and "Logout" links
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Open ``tutorial/templates/layout.jinja2`` and add the following code as
+indicated by the highlighted lines.
+
+.. literalinclude:: src/authentication/tutorial/templates/layout.jinja2
+ :lines: 35-46
+ :lineno-match:
+ :emphasize-lines: 2-10
+ :language: html
+
+The ``request.user`` will be ``None`` if the user is not authenticated, or a
+``tutorial.models.User`` object if the user is authenticated. This check will
+make the logout link shown only when the user is logged in, and conversely the
+login link is only shown when the user is logged out.
+
+
+Viewing the application in a browser
+------------------------------------
+
+We can finally examine our application in a browser (See
+:ref:`wiki2-start-the-application`). Launch a browser and visit each of the
+following URLs, checking that the result is as expected:
+
+- http://localhost:6543/ invokes the ``view_wiki`` view. This always
+ redirects to the ``view_page`` view of the ``FrontPage`` page object. It
+ is executable by any user.
+
+- http://localhost:6543/FrontPage invokes the ``view_page`` view of the
+ ``FrontPage`` page object. There is a "Login" link in the upper right corner
+ while the user is not authenticated, else it is a "Logout" link when the user
+ is authenticated.
+
+- http://localhost:6543/FrontPage/edit_page invokes the ``edit_page`` view for
+ the ``FrontPage`` page object. It is executable by only the ``editor`` user.
+ If a different user (or the anonymous user) invokes it, then a login form
+ will be displayed. Supplying the credentials with the username ``editor`` and
+ password ``editor`` will display the edit page form.
+
+- http://localhost:6543/add_page/SomePageName invokes the ``add_page`` view for
+ a page. If the page already exists, then it redirects the user to the
+ ``edit_page`` view for the page object. It is executable by either the
+ ``editor`` or ``basic`` user. If a different user (or the anonymous user)
+ invokes it, then a login form will be displayed. Supplying the credentials
+ with either the username ``editor`` and password ``editor``, or username
+ ``basic`` and password ``basic``, will display the edit page form.
+
+- http://localhost:6543/SomePageName/edit_page invokes the ``edit_page`` view
+ for an existing page, or generates an error if the page does not exist. It is
+ editable by the ``basic`` user if the page was created by that user in the
+ previous step. If, instead, the page was created by the ``editor`` user, then
+ the login page should be shown for the ``basic`` user.
+
+- After logging in (as a result of hitting an edit or add page and submitting
+ the login form with the ``editor`` credentials), we'll see a "Logout" link in
+ the upper right hand corner. When we click it, we're logged out, redirected
+ back to the front page, and a "Login" link is shown in the upper right hand
+ corner.
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index 1d810b05b..234f40e3b 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -4,375 +4,221 @@
Adding authorization
====================
-:app:`Pyramid` provides facilities for :term:`authentication` and
-:term:`authorization`. We'll make use of both features to provide security
-to our application. Our application currently allows anyone with access to
-the server to view, edit, and add pages to our wiki. We'll change that to
-allow only people who are members of a *group* named ``group:editors`` to add
-and edit wiki pages but we'll continue allowing anyone with access to the
-server to view pages.
-
-We will also add a login page and a logout link on all the pages. The login
-page will be shown when a user is denied access to any of the views that
-require permission, instead of a default "403 Forbidden" page.
-
-We will implement the access control with the following steps:
-
-* Add users and groups (``security.py``, a new module).
-* Add an :term:`ACL` (``models.py`` and ``__init__.py``).
-* Add an :term:`authentication policy` and an :term:`authorization policy`
- (``__init__.py``).
-* Add :term:`permission` declarations to the ``edit_page`` and ``add_page``
- views (``views.py``).
-
-Then we will add the login and logout feature:
-
-* Add routes for /login and /logout (``__init__.py``).
-* Add ``login`` and ``logout`` views (``views.py``).
-* Add a login template (``login.pt``).
-* Make the existing views return a ``logged_in`` flag to the renderer
- (``views.py``).
-* Add a "Logout" link to be shown when logged in and viewing or editing a page
- (``view.pt``, ``edit.pt``).
-
-
-Access control
---------------
-
-Add users and groups
-~~~~~~~~~~~~~~~~~~~~
-
-Create a new ``tutorial/tutorial/security.py`` module with the
-following content:
+In the last chapter we built :term:`authentication` into our wiki. We also
+went one step further and used the ``request.user`` object to perform some
+explicit :term:`authorization` checks. This is fine for a lot of applications,
+but :app:`Pyramid` provides some facilities for cleaning this up and decoupling
+the constraints from the view function itself.
+
+We will implement access control with the following steps:
+
+* Update the :term:`authentication policy` to break down the :term:`userid`
+ into a list of :term:`principals <principal>` (``security.py``).
+* Define an :term:`authorization policy` for mapping users, resources and
+ permissions (``security.py``).
+* Add new :term:`resource` definitions that will be used as the :term:`context`
+ for the wiki pages (``routes.py``).
+* Add an :term:`ACL` to each resource (``routes.py``).
+* Replace the inline checks on the views with :term:`permission` declarations
+ (``views/default.py``).
+
+
+Add user principals
+-------------------
+
+A :term:`principal` is a level of abstraction on top of the raw :term:`userid`
+that describes the user in terms of its capabilities, roles, or other
+identifiers that are easier to generalize. The permissions are then written
+against the principals without focusing on the exact user involved.
+
+:app:`Pyramid` defines two builtin principals used in every application:
+:attr:`pyramid.security.Everyone` and :attr:`pyramid.security.Authenticated`.
+On top of these we have already mentioned the required principals for this
+application in the original design. The user has two possible roles: ``editor``
+or ``basic``. These will be prefixed by the string ``role:`` to avoid clashing
+with any other types of principals.
+
+Open the file ``tutorial/security.py`` and edit it as follows:
.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
+ :emphasize-lines: 3-6,17-24
:language: python
-The ``groupfinder`` function accepts a userid and a request and
-returns one of these values:
+Only the highlighted lines need to be added.
-- If the userid exists in the system, it will return a sequence of group
- identifiers (or an empty sequence if the user isn't a member of any groups).
-- If the userid *does not* exist in the system, it will return ``None``.
+Note that the role comes from the ``User`` object. We also add the ``user.id``
+as a principal for when we want to allow that exact user to edit pages which
+they have created.
-For example, ``groupfinder('editor', request )`` returns ``['group:editor']``,
-``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin',
-request)`` returns ``None``. We will use ``groupfinder()`` as an
-:term:`authentication policy` "callback" that will provide the
-:term:`principal` or principals for a user.
-In a production system, user and group data will most often come from a
-database, but here we use "dummy" data to represent user and groups sources.
+Add the authorization policy
+----------------------------
-Add an ACL
-~~~~~~~~~~
+We already added the :term:`authorization policy` in the previous chapter
+because :app:`Pyramid` requires one when adding an
+:term:`authentication policy`. However, it was not used anywhere, so we'll
+mention it now.
-Open ``tutorial/tutorial/models.py`` and add the following import
-statement at the head:
+In the file ``tutorial/security.py``, notice the following lines:
-.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 1-4
- :linenos:
+.. literalinclude:: src/authorization/tutorial/security.py
+ :lines: 38-40
+ :lineno-match:
+ :emphasize-lines: 2
:language: python
-Add the following class definition at the end:
+We're using the :class:`pyramid.authorization.ACLAuthorizationPolicy`, which
+will suffice for most applications. It uses the :term:`context` to define the
+mapping between a :term:`principal` and :term:`permission` for the current
+request via the ``__acl__``.
-.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 33-37
- :linenos:
- :lineno-start: 33
- :language: python
-We import :data:`~pyramid.security.Allow`, an action that means that
-permission is allowed, and :data:`~pyramid.security.Everyone`, a special
-:term:`principal` that is associated to all requests. Both are used in the
-:term:`ACE` entries that make up the ACL.
+Add resources and ACLs
+----------------------
-The ACL is a list that needs to be named `__acl__` and be an attribute of a
-class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry
-allows any user the `view` permission. The second entry allows the
-``group:editors`` principal the `edit` permission.
+Resources are the hidden gem of :app:`Pyramid`. You've made it!
-The ``RootFactory`` class that contains the ACL is a :term:`root factory`. We
-need to associate it to our :app:`Pyramid` application, so the ACL is provided
-to each view in the :term:`context` of the request as the ``context``
-attribute.
+Every URL in a web application represents a :term:`resource` (the "R" in
+Uniform Resource Locator). Often the resource is something in your data model,
+but it could also be an abstraction over many models.
-Open ``tutorial/tutorial/__init__.py`` and add a ``root_factory`` parameter to
-our :term:`Configurator` constructor, that points to the class we created
-above:
+Our wiki has two resources:
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 24-25
- :linenos:
- :emphasize-lines: 2
- :lineno-start: 16
- :language: python
+#. A ``NewPage``. Represents a potential ``Page`` that does not exist. Any
+ logged-in user, having either role of ``basic`` or ``editor``, can create
+ pages.
-Only the highlighted line needs to be added.
+#. A ``PageResource``. Represents a ``Page`` that is to be viewed or edited.
+ ``editor`` users, as well as the original creator of the ``Page``, may edit
+ the ``PageResource``. Anyone may view it.
-We are now providing the ACL to the application. See :ref:`assigning_acls`
-for more information about what an :term:`ACL` represents.
+.. note::
-.. note:: Although we don't use the functionality here, the ``factory`` used
- to create route contexts may differ per-route as opposed to globally. See
- the ``factory`` argument to :meth:`pyramid.config.Configurator.add_route`
- for more info.
+ The wiki data model is simple enough that the ``PageResource`` is mostly
+ redundant with our ``models.Page`` SQLAlchemy class. It is completely valid
+ to combine these into one class. However, for this tutorial, they are
+ explicitly separated to make clear the distinction between the parts about
+ which :app:`Pyramid` cares versus application-defined objects.
-Add authentication and authorization policies
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+There are many ways to define these resources, and they can even be grouped
+into collections with a hierarchy. However, we're keeping it simple here!
-Open ``tutorial/tutorial/__init__.py`` and add the highlighted import
-statements:
+Open the file ``tutorial/routes.py`` and edit the following lines:
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 1-7
+.. literalinclude:: src/authorization/tutorial/routes.py
:linenos:
- :emphasize-lines: 2-3,7
+ :emphasize-lines: 1-11,17-
:language: python
-Now add those policies to the configuration:
+The highlighted lines need to be edited or added.
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 21-27
- :linenos:
- :lineno-start: 21
- :emphasize-lines: 1-3,6-7
- :language: python
+The ``NewPage`` class has an ``__acl__`` on it that returns a list of mappings
+from :term:`principal` to :term:`permission`. This defines *who* can do *what*
+with that :term:`resource`. In our case we want to allow only those users with
+the principals of either ``role:editor`` or ``role:basic`` to have the
+``create`` permission:
-Only the highlighted lines need to be added.
-
-We are enabling an ``AuthTktAuthenticationPolicy``, which is based in an auth
-ticket that may be included in the request. We are also enabling an
-``ACLAuthorizationPolicy``, which uses an ACL to determine the *allow* or
-*deny* outcome for a view.
-
-Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
-constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is
-a string representing an encryption key used by the "authentication ticket"
-machinery represented by this policy: it is required. The ``callback`` is the
-``groupfinder()`` function that we created before.
-
-Add permission declarations
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/tutorial/views.py`` and add a ``permission='edit'`` parameter
-to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
-
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 60-61
- :emphasize-lines: 1-2
+.. literalinclude:: src/authorization/tutorial/routes.py
+ :lines: 30-38
+ :lineno-match:
+ :emphasize-lines: 5-9
:language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 75-76
- :emphasize-lines: 1-2
- :language: python
-
-Only the highlighted lines, along with their preceding commas, need to be
-edited and added.
-
-The result is that only users who possess the ``edit`` permission at the time
-of the request may invoke those two views.
+The ``NewPage`` is loaded as the :term:`context` of the ``add_page`` route by
+declaring a ``factory`` on the route:
-Add a ``permission='view'`` parameter to the ``@view_config`` decorator for
-``view_wiki()`` and ``view_page()`` as follows:
-
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 30-31
+.. literalinclude:: src/authorization/tutorial/routes.py
+ :lines: 18-19
+ :lineno-match:
:emphasize-lines: 1-2
:language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 36-37
- :emphasize-lines: 1-2
- :language: python
-
-Only the highlighted lines, along with their preceding commas, need to be
-edited and added.
-
-This allows anyone to invoke these two views.
-
-We are done with the changes needed to control access. The changes that
-follow will add the login and logout feature.
-
-Login, logout
--------------
-
-Add routes for /login and /logout
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Go back to ``tutorial/tutorial/__init__.py`` and add these two routes as
-highlighted:
-
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 30-33
- :emphasize-lines: 2-3
- :language: python
-
-.. note:: The preceding lines must be added *before* the following
- ``view_page`` route definition:
-
- .. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 33
- :language: python
-
- This is because ``view_page``'s route definition uses a catch-all
- "replacement marker" ``/{pagename}`` (see :ref:`route_pattern_syntax`)
- which will catch any route that was not already caught by any route listed
- above it in ``__init__.py``. Hence, for ``login`` and ``logout`` views to
- have the opportunity of being matched (or "caught"), they must be above
- ``/{pagename}``.
-
-Add login and logout views
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+The ``PageResource`` class defines the :term:`ACL` for a ``Page``. It uses an
+actual ``Page`` object to determine *who* can do *what* to the page.
-We'll add a ``login`` view which renders a login form and processes the post
-from the login form, checking credentials.
-
-We'll also add a ``logout`` view callable to our application and provide a
-link to it. This view will clear the credentials of the logged in user and
-redirect back to the front page.
-
-Add the following import statements to the head of
-``tutorial/tutorial/views.py``:
-
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 9-19
- :emphasize-lines: 1-11
+.. literalinclude:: src/authorization/tutorial/routes.py
+ :lines: 47-
+ :lineno-match:
+ :emphasize-lines: 5-10
:language: python
-All the highlighted lines need to be added or edited.
-
-:meth:`~pyramid.view.forbidden_view_config` will be used to customize the
-default 403 Forbidden page. :meth:`~pyramid.security.remember` and
-:meth:`~pyramid.security.forget` help to create and expire an auth ticket
-cookie.
-
-Now add the ``login`` and ``logout`` views at the end of the file:
+The ``PageResource`` is loaded as the :term:`context` of the ``view_page`` and
+``edit_page`` routes by declaring a ``factory`` on the routes:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 91-123
+.. literalinclude:: src/authorization/tutorial/routes.py
+ :lines: 17-21
+ :lineno-match:
+ :emphasize-lines: 1,4-5
:language: python
-``login()`` has two decorators:
-- a ``@view_config`` decorator which associates it with the ``login`` route
- and makes it visible when we visit ``/login``,
-- a ``@forbidden_view_config`` decorator which turns it into a
- :term:`forbidden view`. ``login()`` will be invoked when a user tries to
- execute a view callable for which they lack authorization. For example, if
- a user has not logged in and tries to add or edit a Wiki page, they will be
- shown the login form before being allowed to continue.
+Add view permissions
+--------------------
-The order of these two :term:`view configuration` decorators is unimportant.
+At this point we've modified our application to load the ``PageResource``,
+including the actual ``Page`` model in the ``page_factory``. The
+``PageResource`` is now the :term:`context` for all ``view_page`` and
+``edit_page`` views. Similarly the ``NewPage`` will be the context for the
+``add_page`` view.
-``logout()`` is decorated with a ``@view_config`` decorator which associates
-it with the ``logout`` route. It will be invoked when we visit ``/logout``.
+Open the file ``tutorial/views/default.py``.
-Add the ``login.pt`` Template
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+First, you can drop a few imports that are no longer necessary:
-Create ``tutorial/tutorial/templates/login.pt`` with the following content:
-
-.. literalinclude:: src/authorization/tutorial/templates/login.pt
- :language: html
-
-The above template is referenced in the login view that we just added in
-``views.py``.
-
-Return a ``logged_in`` flag to the renderer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Open ``tutorial/tutorial/views.py`` again. Add a ``logged_in`` parameter to
-the return value of ``view_page()``, ``edit_page()``, and ``add_page()`` as
-follows:
-
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 57-58
- :emphasize-lines: 1-2
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 5-7
+ :lineno-match:
+ :emphasize-lines: 1
:language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 72-73
- :emphasize-lines: 1-2
- :language: python
+Edit the ``view_page`` view to declare the ``view`` permission, and remove the
+explicit checks within the view:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 85-89
- :emphasize-lines: 3-4
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 18-23
+ :lineno-match:
+ :emphasize-lines: 1-2,4
:language: python
-Only the highlighted lines need to be added or edited.
-
-The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if
-the user is not authenticated, or a userid if the user is authenticated.
-
-Add a "Logout" link when logged in
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The work of loading the page has already been done in the factory, so we can
+just pull the ``page`` object out of the ``PageResource``, loaded as
+``request.context``. Our factory also guarantees we will have a ``Page``, as it
+raises the ``HTTPNotFound`` exception if no ``Page`` exists, again simplifying
+the view logic.
-Open ``tutorial/tutorial/templates/edit.pt`` and
-``tutorial/tutorial/templates/view.pt`` and add the following code as
-indicated by the highlighted lines.
+Edit the ``edit_page`` view to declare the ``edit`` permission:
-.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :lines: 34-38
- :emphasize-lines: 3-5
- :language: html
-
-The attribute ``tal:condition="logged_in"`` will make the element be included
-when ``logged_in`` is any user id. The link will invoke the logout view. The
-above element will not be included if ``logged_in`` is ``None``, such as when
-a user is not authenticated.
-
-Reviewing our changes
----------------------
-
-Our ``tutorial/tutorial/__init__.py`` will look like this when we're done:
-
-.. literalinclude:: src/authorization/tutorial/__init__.py
- :linenos:
- :emphasize-lines: 2-3,7,21-23,25-27,31-32
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 38-42
+ :lineno-match:
+ :emphasize-lines: 1-2,4
:language: python
-Only the highlighted lines need to be added or edited.
+Edit the ``add_page`` view to declare the ``create`` permission:
-Our ``tutorial/tutorial/models.py`` will look like this when we're done:
-
-.. literalinclude:: src/authorization/tutorial/models.py
- :linenos:
- :emphasize-lines: 1-4,33-37
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 52-56
+ :lineno-match:
+ :emphasize-lines: 1-2,4
:language: python
-Only the highlighted lines need to be added or edited.
-
-Our ``tutorial/tutorial/views.py`` will look like this when we're done:
+Note the ``pagename`` here is pulled off of the context instead of
+``request.matchdict``. The factory has done a lot of work for us to hide the
+actual route pattern.
-.. literalinclude:: src/authorization/tutorial/views.py
- :linenos:
- :emphasize-lines: 9-11,14-19,25,31,37,58,61,73,76,88,91-117,119-123
- :language: python
+The ACLs defined on each :term:`resource` are used by the :term:`authorization
+policy` to determine if any :term:`principal` is allowed to have some
+:term:`permission`. If this check fails (for example, the user is not logged
+in) then an ``HTTPForbidden`` exception will be raised automatically. Thus
+we're able to drop those exceptions and checks from the views themselves.
+Rather we've defined them in terms of operations on a resource.
-Only the highlighted lines need to be added or edited.
+The final ``tutorial/views/default.py`` should look like the following:
-Our ``tutorial/tutorial/templates/edit.pt`` template will look like this when
-we're done:
-
-.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :linenos:
- :emphasize-lines: 36-38
- :language: html
-
-Only the highlighted lines need to be added or edited.
-
-Our ``tutorial/tutorial/templates/view.pt`` template will look like this when
-we're done:
-
-.. literalinclude:: src/authorization/tutorial/templates/view.pt
+.. literalinclude:: src/authorization/tutorial/views/default.py
:linenos:
- :emphasize-lines: 36-38
- :language: html
-
-Only the highlighted lines need to be added or edited.
+ :language: python
Viewing the application in a browser
------------------------------------
@@ -386,21 +232,32 @@ following URLs, checking that the result is as expected:
is executable by any user.
- http://localhost:6543/FrontPage invokes the ``view_page`` view of the
- ``FrontPage`` page object.
-
-- http://localhost:6543/FrontPage/edit_page invokes the edit view for the
- FrontPage object. It is executable by only the ``editor`` user. If a
- different user (or the anonymous user) invokes it, a login form will be
- displayed. Supplying the credentials with the username ``editor``, password
- ``editor`` will display the edit page form.
-
-- http://localhost:6543/add_page/SomePageName invokes the add view for a page.
- It is executable by only the ``editor`` user. If a different user (or the
- anonymous user) invokes it, a login form will be displayed. Supplying the
- credentials with the username ``editor``, password ``editor`` will display
- the edit page form.
+ ``FrontPage`` page object. There is a "Login" link in the upper right corner
+ while the user is not authenticated, else it is a "Logout" link when the user
+ is authenticated.
+
+- http://localhost:6543/FrontPage/edit_page invokes the ``edit_page`` view for
+ the ``FrontPage`` page object. It is executable by only the ``editor`` user.
+ If a different user (or the anonymous user) invokes it, then a login form
+ will be displayed. Supplying the credentials with the username ``editor`` and
+ password ``editor`` will display the edit page form.
+
+- http://localhost:6543/add_page/SomePageName invokes the ``add_page`` view for
+ a page. If the page already exists, then it redirects the user to the
+ ``edit_page`` view for the page object. It is executable by either the
+ ``editor`` or ``basic`` user. If a different user (or the anonymous user)
+ invokes it, then a login form will be displayed. Supplying the credentials
+ with either the username ``editor`` and password ``editor``, or username
+ ``basic`` and password ``basic``, will display the edit page form.
+
+- http://localhost:6543/SomePageName/edit_page invokes the ``edit_page`` view
+ for an existing page, or generates an error if the page does not exist. It is
+ editable by the ``basic`` user if the page was created by that user in the
+ previous step. If, instead, the page was created by the ``editor`` user, then
+ the login page should be shown for the ``basic`` user.
- After logging in (as a result of hitting an edit or add page and submitting
- the login form with the ``editor`` credentials), we'll see a Logout link in
- the upper right hand corner. When we click it, we're logged out, and
- redirected back to the front page.
+ the login form with the ``editor`` credentials), we'll see a "Logout" link in
+ the upper right hand corner. When we click it, we're logged out, redirected
+ back to the front page, and a "Login" link is shown in the upper right hand
+ corner.
diff --git a/docs/tutorials/wiki2/background.rst b/docs/tutorials/wiki2/background.rst
index b8afb8305..ee7dfe36f 100644
--- a/docs/tutorials/wiki2/background.rst
+++ b/docs/tutorials/wiki2/background.rst
@@ -1,3 +1,5 @@
+.. _wiki2_background:
+
==========
Background
==========
@@ -5,13 +7,13 @@ Background
This version of the :app:`Pyramid` wiki tutorial presents a
:app:`Pyramid` application that uses technologies which will be
familiar to someone with SQL database experience. It uses
-:term:`SQLAlchemy` as a persistence mechanism and :term:`url dispatch` to map
+:term:`SQLAlchemy` as a persistence mechanism and :term:`URL dispatch` to map
URLs to code. It can also be followed by people without any prior
Python web framework experience.
To code along with this tutorial, the developer will need a UNIX
machine with development tools (Mac OS X with XCode, any Linux or BSD
-variant, etc) *or* a Windows system of any kind.
+variant, etc.) *or* a Windows system of any kind.
.. note::
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 695d7f15b..ce67bb9e3 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -1,3 +1,5 @@
+.. _wiki2_basic_layout:
+
============
Basic Layout
============
@@ -12,230 +14,237 @@ Application configuration with ``__init__.py``
A directory on disk can be turned into a Python :term:`package` by containing
an ``__init__.py`` file. Even if empty, this marks a directory as a Python
-package. We use ``__init__.py`` both as a marker, indicating the directory
-in which it's contained is a package, and to contain application configuration
+package. We use ``__init__.py`` both as a marker, indicating the directory in
+which it's contained is a package, and to contain application configuration
code.
-Open ``tutorial/tutorial/__init__.py``. It should already contain
-the following:
+Open ``tutorial/__init__.py``. It should already contain the following:
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :linenos:
+ :language: py
-Let's go over this piece-by-piece. First, we need some imports to support
-later code:
+Let's go over this piece-by-piece. First we need some imports to support later
+code:
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :end-before: main
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :end-before: main
+ :linenos:
+ :lineno-match:
+ :language: py
``__init__.py`` defines a function named ``main``. Here is the entirety of
the ``main`` function we've defined in our ``__init__.py``:
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :pyobject: main
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :pyobject: main
+ :linenos:
+ :lineno-match:
+ :language: py
When you invoke the ``pserve development.ini`` command, the ``main`` function
above is executed. It accepts some settings and returns a :term:`WSGI`
application. (See :ref:`startup_chapter` for more about ``pserve``.)
-The main function first creates a :term:`SQLAlchemy` database engine using
-:func:`sqlalchemy.engine_from_config` from the ``sqlalchemy.`` prefixed
-settings in the ``development.ini`` file's ``[app:main]`` section.
-This will be a URI (something like ``sqlite://``):
+Next in ``main``, construct a :term:`Configurator` object:
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 7
+ :lineno-match:
+ :language: py
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 13
- :language: py
+``settings`` is passed to the ``Configurator`` as a keyword argument with the
+dictionary values passed as the ``**settings`` argument. This will be a
+dictionary of settings parsed from the ``.ini`` file, which contains
+deployment-related values, such as ``pyramid.reload_templates``,
+``sqlalchemy.url``, and so on.
-``main`` then initializes our SQLAlchemy session object, passing it the
-engine:
+Next include :term:`Jinja2` templating bindings so that we can use renderers
+with the ``.jinja2`` extension within our project.
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 14
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 8
+ :lineno-match:
+ :language: py
-``main`` subsequently initializes our SQLAlchemy declarative ``Base`` object,
-assigning the engine we created to the ``bind`` attribute of it's
-``metadata`` object. This allows table definitions done imperatively
-(instead of declaratively, via a class statement) to work. We won't use any
-such tables in our application, but if you add one later, long after you've
-forgotten about this tutorial, you won't be left scratching your head when it
-doesn't work.
+Next include the the package ``models`` using a dotted Python path. The exact
+setup of the models will be covered later.
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 15
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 9
+ :lineno-match:
+ :language: py
-The next step of ``main`` is to construct a :term:`Configurator` object:
+Next include the ``routes`` module using a dotted Python path. This module will
+be explained in the next section.
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 16
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 10
+ :lineno-match:
+ :language: py
+
+.. note::
+
+ Pyramid's :meth:`pyramid.config.Configurator.include` method is the primary
+ mechanism for extending the configurator and breaking your code into
+ feature-focused modules.
+
+``main`` next calls the ``scan`` method of the configurator
+(:meth:`pyramid.config.Configurator.scan`), which will recursively scan our
+``tutorial`` package, looking for ``@view_config`` and other special
+decorators. When it finds a ``@view_config`` decorator, a view configuration
+will be registered, allowing one of our application URLs to be mapped to some
+code.
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 11
+ :lineno-match:
+ :language: py
+
+Finally ``main`` is finished configuring things, so it uses the
+:meth:`pyramid.config.Configurator.make_wsgi_app` method to return a
+:term:`WSGI` application:
+
+.. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 12
+ :lineno-match:
+ :language: py
-``settings`` is passed to the Configurator as a keyword argument with the
-dictionary values passed as the ``**settings`` argument. This will be a
-dictionary of settings parsed from the ``.ini`` file, which contains
-deployment-related values such as ``pyramid.reload_templates``,
-``db_string``, etc.
-Next, include :term:`Chameleon` templating bindings so that we can use
-renderers with the ``.pt`` extension within our project.
+Route declarations
+------------------
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 17
- :language: py
+Open the ``tutorials/routes.py`` file. It should already contain the following:
-``main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with
-two arguments: ``static`` (the name), and ``static`` (the path):
+.. literalinclude:: src/basiclayout/tutorial/routes.py
+ :linenos:
+ :language: py
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 18
- :language: py
+On line 2, we call :meth:`pyramid.config.Configurator.add_static_view` with
+three arguments: ``static`` (the name), ``static`` (the path), and
+``cache_max_age`` (a keyword argument).
This registers a static resource view which will match any URL that starts
with the prefix ``/static`` (by virtue of the first argument to
-``add_static_view``). This will serve up static resources for us from within
-the ``static`` directory of our ``tutorial`` package, in this case, via
+``add_static_view``). This will serve up static resources for us from within
+the ``static`` directory of our ``tutorial`` package, in this case via
``http://localhost:6543/static/`` and below (by virtue of the second argument
to ``add_static_view``). With this declaration, we're saying that any URL that
starts with ``/static`` should go to the static view; any remainder of its
-path (e.g. the ``/foo`` in ``/static/foo``) will be used to compose a path to
+path (e.g., the ``/foo`` in ``/static/foo``) will be used to compose a path to
a static file resource, such as a CSS file.
-Using the configurator ``main`` also registers a :term:`route configuration`
-via the :meth:`pyramid.config.Configurator.add_route` method that will be
-used when the URL is ``/``:
+On line 3, the module registers a :term:`route configuration` via the
+:meth:`pyramid.config.Configurator.add_route` method that will be used when the
+URL is ``/``. Since this route has a ``pattern`` equaling ``/``, it is the
+route that will be matched when the URL ``/`` is visited, e.g.,
+``http://localhost:6543/``.
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 19
- :language: py
-Since this route has a ``pattern`` equaling ``/`` it is the route that will
-be matched when the URL ``/`` is visited, e.g. ``http://localhost:6543/``.
-
-``main`` next calls the ``scan`` method of the configurator
-(:meth:`pyramid.config.Configurator.scan`), which will recursively scan our
-``tutorial`` package, looking for ``@view_config`` (and
-other special) decorators. When it finds a ``@view_config`` decorator, a
-view configuration will be registered, which will allow one of our
-application URLs to be mapped to some code.
-
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 20
- :language: py
-
-Finally, ``main`` is finished configuring things, so it uses the
-:meth:`pyramid.config.Configurator.make_wsgi_app` method to return a
-:term:`WSGI` application:
-
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 21
- :language: py
-
-View declarations via ``views.py``
-----------------------------------
+View declarations via the ``views`` package
+-------------------------------------------
The main function of a web framework is mapping each URL pattern to code (a
:term:`view callable`) that is executed when the requested URL matches the
corresponding :term:`route`. Our application uses the
:meth:`pyramid.view.view_config` decorator to perform this mapping.
-Open ``tutorial/tutorial/views.py``. It should already contain the following:
+Open ``tutorial/views/default.py`` in the ``views`` package. It should already
+contain the following:
- .. literalinclude:: src/basiclayout/tutorial/views.py
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/views/default.py
+ :linenos:
+ :language: py
The important part here is that the ``@view_config`` decorator associates the
-function it decorates (``my_view``) with a :term:`view configuration`,
+function it decorates (``my_view``) with a :term:`view configuration`,
consisting of:
* a ``route_name`` (``home``)
- * a ``renderer``, which is a template from the ``templates`` subdirectory
- of the package.
+ * a ``renderer``, which is a template from the ``templates`` subdirectory of
+ the package.
When the pattern associated with the ``home`` view is matched during a request,
-``my_view()`` will be executed. ``my_view()`` returns a dictionary; the
-renderer will use the ``templates/mytemplate.pt`` template to create a response
-based on the values in the dictionary.
+``my_view()`` will be executed. ``my_view()`` returns a dictionary; the
+renderer will use the ``templates/mytemplate.jinja2`` template to create a
+response based on the values in the dictionary.
Note that ``my_view()`` accepts a single argument named ``request``. This is
the standard call signature for a Pyramid :term:`view callable`.
Remember in our ``__init__.py`` when we executed the
-:meth:`pyramid.config.Configurator.scan` method ``config.scan()``? The
-purpose of calling the scan method was to find and process this
-``@view_config`` decorator in order to create a view configuration within our
-application. Without being processed by ``scan``, the decorator effectively
-does nothing. ``@view_config`` is inert without being detected via a
-:term:`scan`.
-
-The sample ``my_view()`` created by the scaffold uses a ``try:`` and ``except:``
-clause to detect if there is a problem accessing the project database and
-provide an alternate error response. That response will include the text
-shown at the end of the file, which will be displayed in the browser to
-inform the user about possible actions to take to solve the problem.
-
-Content Models with ``models.py``
----------------------------------
-
-In a SQLAlchemy-based application, a *model* object is an object composed by
-querying the SQL database. The ``models.py`` file is where the ``alchemy``
-scaffold put the classes that implement our models.
+:meth:`pyramid.config.Configurator.scan` method ``config.scan()``? The purpose
+of calling the scan method was to find and process this ``@view_config``
+decorator in order to create a view configuration within our application.
+Without being processed by ``scan``, the decorator effectively does nothing.
+``@view_config`` is inert without being detected via a :term:`scan`.
-Open ``tutorial/tutorial/models.py``. It should already contain the following:
+The sample ``my_view()`` created by the scaffold uses a ``try:`` and
+``except:`` clause to detect if there is a problem accessing the project
+database and provide an alternate error response. That response will include
+the text shown at the end of the file, which will be displayed in the browser
+to inform the user about possible actions to take to solve the problem.
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :linenos:
- :language: py
-Let's examine this in detail. First, we need some imports to support later code:
+Content models with the ``models`` package
+------------------------------------------
+
+In an SQLAlchemy-based application, a *model* object is an object composed by
+querying the SQL database. The ``models`` package is where the ``alchemy``
+scaffold put the classes that implement our models.
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :end-before: DBSession
- :linenos:
- :language: py
+First, open ``tutorial/models/meta.py``, which should already contain the
+following:
-Next we set up a SQLAlchemy ``DBSession`` object:
+.. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :linenos:
+ :language: py
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :lines: 17
- :language: py
+``meta.py`` contains imports and support code for defining the models. We
+create a dictionary ``NAMING_CONVENTION`` as well for consistent naming of
+support objects like indices and constraints.
-``scoped_session`` and ``sessionmaker`` are standard SQLAlchemy helpers.
-``scoped_session`` allows us to access our database connection globally.
-``sessionmaker`` creates a database session object. We pass to
-``sessionmaker`` the ``extension=ZopeTransactionExtension()`` extension
-option in order to allow the system to automatically manage database
-transactions. With ``ZopeTransactionExtension`` activated, our application
-will automatically issue a transaction commit after every request unless an
-exception is raised, in which case the transaction will be aborted.
+.. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :end-before: metadata
+ :linenos:
+ :language: py
-We also need to create a declarative ``Base`` object to use as a
-base class for our model:
+Next we create a ``metadata`` object from the class
+:class:`sqlalchemy.schema.MetaData`, using ``NAMING_CONVENTION`` as the value
+for the ``naming_convention`` argument.
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :lines: 18
- :language: py
+A ``MetaData`` object represents the table and other schema definitions for a
+single database. We also need to create a declarative ``Base`` object to use as
+a base class for our models. Our models will inherit from this ``Base``, which
+will attach the tables to the ``metadata`` we created, and define our
+application's database schema.
-Our model classes will inherit from this ``Base`` class so they can be
-associated with our particular database connection.
+.. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :lines: 15-16
+ :lineno-match:
+ :linenos:
+ :language: py
-To give a simple example of a model class, we define one named ``MyModel``:
+Next open ``tutorial/models/mymodel.py``, which should already contain the
+following:
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :pyobject: MyModel
- :linenos:
- :language: py
+.. literalinclude:: src/basiclayout/tutorial/models/mymodel.py
+ :linenos:
+ :language: py
+
+Notice we've defined the ``models`` as a package to make it straightforward for
+defining models in separate modules. To give a simple example of a model class,
+we have defined one named ``MyModel`` in ``mymodel.py``:
+
+.. literalinclude:: src/basiclayout/tutorial/models/mymodel.py
+ :pyobject: MyModel
+ :lineno-match:
+ :linenos:
+ :language: py
Our example model does not require an ``__init__`` method because SQLAlchemy
-supplies for us a default constructor if one is not already present,
-which accepts keyword arguments of the same name as that of the mapped attributes.
+supplies for us a default constructor, if one is not already present, which
+accepts keyword arguments of the same name as that of the mapped attributes.
.. note:: Example usage of MyModel:
@@ -247,8 +256,83 @@ The ``MyModel`` class has a ``__tablename__`` attribute. This informs
SQLAlchemy which table to use to store the data representing instances of this
class.
-The Index import and the Index object creation is not required for this
-tutorial, and will be removed in the next step.
+Finally, open ``tutorial/models/__init__.py``, which should already
+contain the following:
+
+.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
+ :linenos:
+ :language: py
+
+Our ``models/__init__.py`` module defines the primary API we will use for
+configuring the database connections within our application, and it contains
+several functions we will cover below.
+
+As we mentioned above, the purpose of the ``models.meta.metadata`` object is to
+describe the schema of the database. This is done by defining models that
+inherit from the ``Base`` object attached to that ``metadata`` object. In
+Python, code is only executed if it is imported, and so to attach the
+``models`` table defined in ``mymodel.py`` to the ``metadata``, we must import
+it. If we skip this step, then later, when we run
+:meth:`sqlalchemy.schema.MetaData.create_all`, the table will not be created
+because the ``metadata`` object does not know about it!
+
+Another important reason to import all of the models is that, when defining
+relationships between models, they must all exist in order for SQLAlchemy to
+find and build those internal mappings. This is why, after importing all the
+models, we explicitly execute the function
+:func:`sqlalchemy.orm.configure_mappers`, once we are sure all the models have
+been defined and before we start creating connections.
+
+Next we define several functions for connecting to our database. The first and
+lowest level is the ``get_engine`` function. This creates an :term:`SQLAlchemy`
+database engine using :func:`sqlalchemy.engine_from_config` from the
+``sqlalchemy.``-prefixed settings in the ``development.ini`` file's
+``[app:main]`` section. This setting is a URI (something like ``sqlite://``).
+
+.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
+ :pyobject: get_engine
+ :lineno-match:
+ :linenos:
+ :language: py
+
+The function ``get_session_factory`` accepts an :term:`SQLAlchemy` database
+engine, and creates a ``session_factory`` from the :term:`SQLAlchemy` class
+:class:`sqlalchemy.orm.session.sessionmaker`. This ``session_factory`` is then
+used for creating sessions bound to the database engine.
+
+.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
+ :pyobject: get_session_factory
+ :lineno-match:
+ :linenos:
+ :language: py
+
+The function ``get_tm_session`` registers a database session with a transaction
+manager, and returns a ``dbsession`` object. With the transaction manager, our
+application will automatically issue a transaction commit after every request,
+unless an exception is raised, in which case the transaction will be aborted.
+
+.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
+ :pyobject: get_tm_session
+ :lineno-match:
+ :linenos:
+ :language: py
+
+Finally, we define an ``includeme`` function, which is a hook for use with
+:meth:`pyramid.config.Configurator.include` to activate code in a Pyramid
+application add-on. It is the code that is executed above when we ran
+``config.include('.models')`` in our application's ``main`` function. This
+function will take the settings from the application, create an engine, and
+define a ``request.dbsession`` property, which we can use to do work on behalf
+of an incoming request to our application.
+
+.. literalinclude:: src/basiclayout/tutorial/models/__init__.py
+ :pyobject: includeme
+ :lineno-match:
+ :linenos:
+ :language: py
That's about all there is to it regarding models, views, and initialization
code in our stock application.
+
+The ``Index`` import and the ``Index`` object creation in ``mymodel.py`` is
+not required for this tutorial, and will be removed in the next step.
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst
index b2d9bf83a..9f7b82d1d 100644
--- a/docs/tutorials/wiki2/definingmodels.rst
+++ b/docs/tutorials/wiki2/definingmodels.rst
@@ -1,127 +1,263 @@
+.. _wiki2_defining_the_domain_model:
+
=========================
Defining the Domain Model
=========================
The first change we'll make to our stock ``pcreate``-generated application will
-be to define a :term:`domain model` constructor representing a wiki page.
-We'll do this inside our ``models.py`` file.
+be to define a wiki page :term:`domain model`.
+.. note::
-Edit ``models.py``
-------------------
+ There is nothing special about the filename ``user.py`` or ``page.py`` except
+ that they are Python modules. A project may have many models throughout its
+ codebase in arbitrarily named modules. Modules implementing models often
+ have ``model`` in their names or they may live in a Python subpackage of
+ your application package named ``models`` (as we've done in this tutorial),
+ but this is only a convention and not a requirement.
-.. note::
- There is nothing special about the filename ``models.py``. A
- project may have many models throughout its codebase in arbitrarily named
- files. Files implementing models often have ``model`` in their filenames
- or they may live in a Python subpackage of your application package named
- ``models``, but this is only by convention.
+Declaring dependencies in our ``setup.py`` file
+===============================================
+
+The models code in our application will depend on a package which is not a
+dependency of the original "tutorial" application. The original "tutorial"
+application was generated by the ``pcreate`` command; it doesn't know about our
+custom application requirements.
+
+We need to add a dependency, the ``bcrypt`` package, to our ``tutorial``
+package's ``setup.py`` file by assigning this dependency to the ``requires``
+parameter in the ``setup()`` function.
+
+Open ``tutorial/setup.py`` and edit it to look like the following:
+
+.. literalinclude:: src/models/setup.py
+ :linenos:
+ :emphasize-lines: 12
+ :language: python
+
+Only the highlighted line needs to be added.
+
+
+Running ``pip install -e .``
+============================
+
+Since a new software dependency was added, you will need to run ``pip install
+-e .`` again inside the root of the ``tutorial`` package to obtain and register
+the newly added dependency distribution.
+
+Make sure your current working directory is the root of the project (the
+directory in which ``setup.py`` lives) and execute the following command.
+
+On UNIX:
+
+.. code-block:: bash
+
+ $ cd tutorial
+ $ $VENV/bin/pip install -e .
+
+On Windows:
+
+.. code-block:: doscon
+
+ c:\pyramidtut> cd tutorial
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
+
+Success executing this command will end with a line to the console something
+like this::
+
+ Successfully installed bcrypt-2.0.0 cffi-1.5.2 pycparser-2.14 tutorial-0.0
+
+
+Remove ``mymodel.py``
+---------------------
+
+Let's delete the file ``tutorial/models/mymodel.py``. The ``MyModel`` class is
+only a sample and we're not going to use it.
+
+
+Add ``user.py``
+---------------
+
+Create a new file ``tutorial/models/user.py`` with the following contents:
+
+.. literalinclude:: src/models/tutorial/models/user.py
+ :linenos:
+ :language: py
+
+This is a very basic model for a user who can authenticate with our wiki.
+
+We discussed briefly in the previous chapter that our models will inherit from
+an SQLAlchemy :func:`sqlalchemy.ext.declarative.declarative_base`. This will
+attach the model to our schema.
+
+As you can see, our ``User`` class has a class-level attribute
+``__tablename__`` which equals the string ``users``. Our ``User`` class will
+also have class-level attributes named ``id``, ``name``, ``password_hash``,
+and ``role`` (all instances of :class:`sqlalchemy.schema.Column`). These will
+map to columns in the ``users`` table. The ``id`` attribute will be the primary
+key in the table. The ``name`` attribute will be a text column, each value of
+which needs to be unique within the column. The ``password_hash`` is a nullable
+text attribute that will contain a securely hashed password [1]_. Finally, the
+``role`` text attribute will hold the role of the user.
+
+There are two helper methods that will help us later when using the user
+objects. The first is ``set_password`` which will take a raw password and
+transform it using bcrypt_ into an irreversible representation, a process known
+as "hashing". The second method, ``check_password``, will allow us to compare
+the hashed value of the submitted password against the hashed value of the
+password stored in the user's record in the database. If the two hashed values
+match, then the submitted password is valid, and we can authenticate the user.
+
+We hash passwords so that it is impossible to decrypt them and use them to
+authenticate in the application. If we stored passwords foolishly in clear
+text, then anyone with access to the database could retrieve any password to
+authenticate as any user.
-Open ``tutorial/tutorial/models.py`` file and edit it to look like the
-following:
-.. literalinclude:: src/models/tutorial/models.py
+Add ``page.py``
+---------------
+
+Create a new file ``tutorial/models/page.py`` with the following contents:
+
+.. literalinclude:: src/models/tutorial/models/page.py
:linenos:
:language: py
- :emphasize-lines: 20-22,24,25
-The highlighted lines are the ones that need to be changed, as well as
-removing lines that reference ``Index``.
+As you can see, our ``Page`` class is very similar to the ``User`` defined
+above, except with attributes focused on storing information about a wiki page,
+including ``id``, ``name``, and ``data``. The only new construct introduced
+here is the ``creator_id`` column, which is a foreign key referencing the
+``users`` table. Foreign keys are very useful at the schema-level, but since we
+want to relate ``User`` objects with ``Page`` objects, we also define a
+``creator`` attribute as an ORM-level mapping between the two tables.
+SQLAlchemy will automatically populate this value using the foreign key
+referencing the user. Since the foreign key has ``nullable=False``, we are
+guaranteed that an instance of ``page`` will have a corresponding
+``page.creator``, which will be a ``User`` instance.
-The first thing we've done is remove the stock ``MyModel`` class
-from the generated ``models.py`` file. The ``MyModel`` class is only a
-sample and we're not going to use it.
-Then, we added a ``Page`` class. Because this is a SQLAlchemy application,
-this class inherits from an instance of
-:func:`sqlalchemy.ext.declarative.declarative_base`.
+Edit ``models/__init__.py``
+---------------------------
-.. literalinclude:: src/models/tutorial/models.py
- :pyobject: Page
+Since we are using a package for our models, we also need to update our
+``__init__.py`` file to ensure that the models are attached to the metadata.
+
+Open the ``tutorial/models/__init__.py`` file and edit it to look like
+the following:
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
:linenos:
- :language: python
+ :language: py
+ :emphasize-lines: 8,9
-As you can see, our ``Page`` class has a class level attribute
-``__tablename__`` which equals the string ``'pages'``. This means that
-SQLAlchemy will store our wiki data in a SQL table named ``pages``. Our
-``Page`` class will also have class-level attributes named ``id``, ``name``
-and ``data`` (all instances of :class:`sqlalchemy.schema.Column`). These will
-map to columns in the ``pages`` table. The ``id`` attribute will be the
-primary key in the table. The ``name`` attribute will be a text attribute,
-each value of which needs to be unique within the column. The ``data``
-attribute is a text attribute that will hold the body of each page.
+Here we align our imports with the names of the models, ``Page`` and ``User``.
-Changing ``scripts/initializedb.py``
-------------------------------------
+
+Edit ``scripts/initializedb.py``
+--------------------------------
We haven't looked at the details of this file yet, but within the ``scripts``
directory of your ``tutorial`` package is a file named ``initializedb.py``.
Code in this file is executed whenever we run the ``initialize_tutorial_db``
-command, as we did in the installation step of this tutorial.
+command, as we did in the installation step of this tutorial [2]_.
Since we've changed our model, we need to make changes to our
``initializedb.py`` script. In particular, we'll replace our import of
-``MyModel`` with one of ``Page`` and we'll change the very end of the script
-to create a ``Page`` rather than a ``MyModel`` and add it to our
-``DBSession``.
+``MyModel`` with those of ``User`` and ``Page``. We'll also change the very end
+of the script to create two ``User`` objects (``basic`` and ``editor``) as well
+as a ``Page``, rather than a ``MyModel``, and add them to our ``dbsession``.
-Open ``tutorial/tutorial/scripts/initializedb.py`` and edit it to look like
-the following:
+Open ``tutorial/scripts/initializedb.py`` and edit it to look like the
+following:
.. literalinclude:: src/models/tutorial/scripts/initializedb.py
:linenos:
:language: python
- :emphasize-lines: 14,31,36
+ :emphasize-lines: 18,44-57
+
+Only the highlighted lines need to be changed.
-Only the highlighted lines need to be changed, as well as removing the lines
-referencing ``pyramid.scripts.common`` and ``options`` under the ``main``
-function.
Installing the project and re-initializing the database
-------------------------------------------------------
-Because our model has changed, in order to reinitialize the database, we need
-to rerun the ``initialize_tutorial_db`` command to pick up the changes you've
-made to both the models.py file and to the initializedb.py file. See
+Because our model has changed, and in order to reinitialize the database, we
+need to rerun the ``initialize_tutorial_db`` command to pick up the changes
+we've made to both the models.py file and to the initializedb.py file. See
:ref:`initialize_db_wiki2` for instructions.
-Success will look something like this::
-
- 2015-05-24 15:34:14,542 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
- 2015-05-24 15:34:14,542 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
- 2015-05-24 15:34:14,543 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
- 2015-05-24 15:34:14,543 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
- 2015-05-24 15:34:14,543 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("pages")
- 2015-05-24 15:34:14,544 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2015-05-24 15:34:14,544 INFO [sqlalchemy.engine.base.Engine:1097][MainThread]
- CREATE TABLE pages (
- id INTEGER NOT NULL,
- name TEXT,
- data TEXT,
- PRIMARY KEY (id),
- UNIQUE (name)
- )
-
-
- 2015-05-24 15:34:14,545 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2015-05-24 15:34:14,546 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
- 2015-05-24 15:34:14,548 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit)
- 2015-05-24 15:34:14,549 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO pages (name, data) VALUES (?, ?)
- 2015-05-24 15:34:14,549 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('FrontPage', 'This is the front page')
- 2015-05-24 15:34:14,550 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+Success will look something like this:
+
+.. code-block:: bash
+
+ 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
+ 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
+ 2016-05-22 04:12:09,226 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
+ 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
+ 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("users")
+ 2016-05-22 04:12:09,227 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
+ 2016-05-22 04:12:09,228 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("pages")
+ 2016-05-22 04:12:09,228 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
+ 2016-05-22 04:12:09,229 INFO [sqlalchemy.engine.base.Engine:1097][MainThread]
+ CREATE TABLE users (
+ id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ role TEXT NOT NULL,
+ password_hash TEXT,
+ CONSTRAINT pk_users PRIMARY KEY (id),
+ CONSTRAINT uq_users_name UNIQUE (name)
+ )
+
+
+ 2016-05-22 04:12:09,229 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
+ 2016-05-22 04:12:09,230 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+ 2016-05-22 04:12:09,230 INFO [sqlalchemy.engine.base.Engine:1097][MainThread]
+ CREATE TABLE pages (
+ id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ data TEXT NOT NULL,
+ creator_id INTEGER NOT NULL,
+ CONSTRAINT pk_pages PRIMARY KEY (id),
+ CONSTRAINT uq_pages_name UNIQUE (name),
+ CONSTRAINT fk_pages_creator_id_users FOREIGN KEY(creator_id) REFERENCES users (id)
+ )
+
+
+ 2016-05-22 04:12:09,231 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
+ 2016-05-22 04:12:09,231 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+ 2016-05-22 04:12:09,782 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit)
+ 2016-05-22 04:12:09,783 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?)
+ 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('editor', 'editor', b'$2b$12$K/WLVKRl5fMAb6UM58ueTetXlE3rlc5cRK5zFPimK598scXBR/xWC')
+ 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO users (name, role, password_hash) VALUES (?, ?, ?)
+ 2016-05-22 04:12:09,784 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('basic', 'basic', b'$2b$12$JfwLyCJGv3t.RTSmIrh3B.FKXRT9FevkAqafWdK5oq7Hl4mgAQORe')
+ 2016-05-22 04:12:09,785 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO pages (name, data, creator_id) VALUES (?, ?, ?)
+ 2016-05-22 04:12:09,785 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('FrontPage', 'This is the front page', 1)
+ 2016-05-22 04:12:09,786 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+
View the application in a browser
---------------------------------
We can't. At this point, our system is in a "non-runnable" state; we'll need
to change view-related files in the next chapter to be able to start the
-application successfully. If you try to start the application (See
-:ref:`wiki2-start-the-application`), you'll wind
-up with a Python traceback on your console that ends with this exception:
+application successfully. If you try to start the application (see
+:ref:`wiki2-start-the-application`), you'll wind up with a Python traceback on
+your console that ends with this exception:
.. code-block:: text
ImportError: cannot import name MyModel
This will also happen if you attempt to run the tests.
+
+.. _bcrypt: https://pypi.python.org/pypi/bcrypt
+
+.. [1] We are using the bcrypt_ package from PyPI to hash our passwords
+ securely. There are other one-way hash algorithms for passwords if
+ bcrypt is an issue on your system. Just make sure that it's an
+ algorithm approved for storing passwords versus a generic one-way hash.
+
+.. [2] The command is named ``initialize_tutorial_db`` because of the mapping
+ defined in the ``[console_scripts]`` entry point of our project's
+ ``setup.py`` file.
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index 0b495445a..996bff88c 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -1,12 +1,14 @@
+.. _wiki2_defining_views:
+
==============
Defining Views
==============
A :term:`view callable` in a :app:`Pyramid` application is typically a simple
-Python function that accepts a single parameter named :term:`request`. A
-view callable is assumed to return a :term:`response` object.
+Python function that accepts a single parameter named :term:`request`. A view
+callable is assumed to return a :term:`response` object.
-The request object has a dictionary as an attribute named ``matchdict``. A
+The request object has a dictionary as an attribute named ``matchdict``. A
``matchdict`` maps the placeholders in the matching URL ``pattern`` to the
substrings of the path in the :term:`request` URL. For instance, if a call to
:meth:`pyramid.config.Configurator.add_route` has the pattern ``/{one}/{two}``,
@@ -14,13 +16,13 @@ and a user visits ``http://example.com/foo/bar``, our pattern would be matched
against ``/foo/bar`` and the ``matchdict`` would look like ``{'one':'foo',
'two':'bar'}``.
-Declaring Dependencies in Our ``setup.py`` File
-===============================================
-The view code in our application will depend on a package which is not a
-dependency of the original "tutorial" application. The original "tutorial"
-application was generated by the ``pcreate`` command; it doesn't know
-about our custom application requirements.
+Adding the ``docutils`` dependency
+==================================
+
+Remember in the previous chapter we added a new dependency of the ``bcrypt``
+package. Again, the view code in our application will depend on a package which
+is not a dependency of the original "tutorial" application.
We need to add a dependency on the ``docutils`` package to our ``tutorial``
package's ``setup.py`` file by assigning this dependency to the ``requires``
@@ -30,109 +32,164 @@ Open ``tutorial/setup.py`` and edit it to look like the following:
.. literalinclude:: src/views/setup.py
:linenos:
- :emphasize-lines: 20
+ :emphasize-lines: 13
:language: python
Only the highlighted line needs to be added.
-Running ``setup.py develop``
-============================
+Again, as we did in the previous chapter, the dependency now needs to be
+installed, so re-run the ``$VENV/bin/pip install -e .`` command.
-Since a new software dependency was added, you will need to run ``python
-setup.py develop`` again inside the root of the ``tutorial`` package to obtain
-and register the newly added dependency distribution.
-Make sure your current working directory is the root of the project (the
-directory in which ``setup.py`` lives) and execute the following command.
+Static assets
+-------------
+
+Our templates name static assets, including CSS and images. We don't need
+to create these files within our package's ``static`` directory because they
+were provided at the time we created the project.
+
+As an example, the CSS file will be accessed via
+``http://localhost:6543/static/theme.css`` by virtue of the call to the
+``add_static_view`` directive we've made in the ``routes.py`` file. Any number
+and type of static assets can be placed in this directory (or subdirectories)
+and are just referred to by URL or by using the convenience method
+``static_url``, e.g., ``request.static_url('<package>:static/foo.css')`` within
+templates.
-On UNIX:
-.. code-block:: text
+Adding routes to ``routes.py``
+==============================
- $ cd tutorial
- $ $VENV/bin/python setup.py develop
+This is the `URL Dispatch` tutorial, so let's start by adding some URL patterns
+to our app. Later we'll attach views to handle the URLs.
-On Windows:
+The ``routes.py`` file contains :meth:`pyramid.config.Configurator.add_route`
+calls which serve to add routes to our application. First we'll get rid of the
+existing route created by the template using the name ``'home'``. It's only an
+example and isn't relevant to our application.
-.. code-block:: text
+We then need to add four calls to ``add_route``. Note that the *ordering* of
+these declarations is very important. Route declarations are matched in the
+order they're registered.
- c:\pyramidtut> cd tutorial
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
+#. Add a declaration which maps the pattern ``/`` (signifying the root URL) to
+ the route named ``view_wiki``. In the next step, we will map it to our
+ ``view_wiki`` view callable by virtue of the ``@view_config`` decorator
+ attached to the ``view_wiki`` view function, which in turn will be indicated
+ by ``route_name='view_wiki'``.
-Success executing this command will end with a line to the console something
-like::
+#. Add a declaration which maps the pattern ``/{pagename}`` to the route named
+ ``view_page``. This is the regular view for a page. Again, in the next step,
+ we will map it to our ``view_page`` view callable by virtue of the
+ ``@view_config`` decorator attached to the ``view_page`` view function,
+ whin in turn will be indicated by ``route_name='view_page'``.
- Finished processing dependencies for tutorial==0.0
+#. Add a declaration which maps the pattern ``/add_page/{pagename}`` to the
+ route named ``add_page``. This is the add view for a new page. We will map
+ it to our ``add_page`` view callable by virtue of the ``@view_config``
+ decorator attached to the ``add_page`` view function, which in turn will be
+ indicated by ``route_name='add_page'``.
-Adding view functions in ``views.py``
-=====================================
+#. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the
+ route named ``edit_page``. This is the edit view for a page. We will map it
+ to our ``edit_page`` view callable by virtue of the ``@view_config``
+ decorator attached to the ``edit_page`` view function, which in turn will be
+ indicated by ``route_name='edit_page'``.
-It's time for a major change. Open ``tutorial/tutorial/views.py`` and edit it
-to look like the following:
+As a result of our edits, the ``routes.py`` file should look like the
+following:
-.. literalinclude:: src/views/tutorial/views.py
+.. literalinclude:: src/views/tutorial/routes.py
:linenos:
+ :emphasize-lines: 3-6
:language: python
- :emphasize-lines: 1-7,14,16-72
+
+The highlighted lines are the ones that need to be added or edited.
+
+.. warning::
+
+ The order of the routes is important! If you placed
+ ``/{pagename}/edit_page`` *before* ``/add_page/{pagename}``, then we would
+ never be able to add pages. This is because the first route would always
+ match a request to ``/add_page/edit_page`` whereas we want ``/add_page/..``
+ to have priority. This isn't a huge problem in this particular app because
+ wiki pages are always camel case, but it's important to be aware of this
+ behavior in your own apps.
+
+
+Adding view functions in ``views/default.py``
+=============================================
+
+It's time for a major change. Open ``tutorial/views/default.py`` and
+edit it to look like the following:
+
+.. literalinclude:: src/views/tutorial/views/default.py
+ :linenos:
+ :language: python
+ :emphasize-lines: 1-9,12-
The highlighted lines need to be added or edited.
-We added some imports and created a regular expression to find "WikiWords".
+We added some imports, and created a regular expression to find "WikiWords".
We got rid of the ``my_view`` view function and its decorator that was added
when we originally rendered the ``alchemy`` scaffold. It was only an example
-and isn't relevant to our application.
+and isn't relevant to our application. We also deleted the ``db_err_msg``
+string.
-Then we added four :term:`view callable` functions to our ``views.py``
-module:
+Then we added four :term:`view callable` functions to our ``views/default.py``
+module, as mentioned in the previous step:
* ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL.
* ``view_page()`` - Displays an individual page.
-* ``add_page()`` - Allows the user to add a page.
* ``edit_page()`` - Allows the user to edit a page.
+* ``add_page()`` - Allows the user to add a page.
We'll describe each one briefly in the following sections.
.. note::
- There is nothing special about the filename ``views.py``. A project may
- have many view callables throughout its codebase in arbitrarily named
- files. Files implementing view callables often have ``view`` in their
- filenames (or may live in a Python subpackage of your application package
- named ``views``), but this is only by convention.
+ There is nothing special about the filename ``default.py`` exept that it is a
+ Python module. A project may have many view callables throughout its codebase
+ in arbitrarily named modules. Modules implementing view callables often have
+ ``view`` in their name (or may live in a Python subpackage of your
+ application package named ``views``, as in our case), but this is only by
+ convention, not a requirement.
+
The ``view_wiki`` view function
-------------------------------
Following is the code for the ``view_wiki`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 20-24
- :lineno-start: 20
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 17-20
+ :lineno-match:
:linenos:
:language: python
``view_wiki()`` is the :term:`default view` that gets called when a request is
-made to the root URL of our wiki. It always redirects to an URL which
+made to the root URL of our wiki. It always redirects to a URL which
represents the path to our "FrontPage".
The ``view_wiki`` view callable always redirects to the URL of a Page resource
named "FrontPage". To do so, it returns an instance of the
:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement
the :class:`pyramid.interfaces.IResponse` interface, like
-:class:`pyramid.response.Response` does). It uses the
-:meth:`pyramid.request.Request.route_url` API to construct an URL to the
+:class:`pyramid.response.Response`). It uses the
+:meth:`pyramid.request.Request.route_url` API to construct a URL to the
``FrontPage`` page (i.e., ``http://localhost:6543/FrontPage``), and uses it as
the "location" of the ``HTTPFound`` response, forming an HTTP redirect.
+
The ``view_page`` view function
-------------------------------
Here is the code for the ``view_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 25-45
- :lineno-start: 25
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 22-42
+ :lineno-match:
:linenos:
:language: python
@@ -141,83 +198,59 @@ Here is the code for the ``view_page`` view function and its decorator:
``Page`` model object) as HTML. Then it substitutes an HTML anchor for each
*WikiWord* reference in the rendered HTML using a compiled regular expression.
-The curried function named ``check`` is used as the first argument to
+The curried function named ``add_link`` is used as the first argument to
``wikiwords.sub``, indicating that it should be called to provide a value for
each WikiWord match found in the content. If the wiki already contains a
-page with the matched WikiWord name, ``check()`` generates a view
+page with the matched WikiWord name, ``add_link()`` generates a view
link to be used as the substitution value and returns it. If the wiki does
-not already contain a page with the matched WikiWord name, ``check()``
+not already contain a page with the matched WikiWord name, ``add_link()``
generates an "add" link as the substitution value and returns it.
As a result, the ``content`` variable is now a fully formed bit of HTML
containing various view and add links for WikiWords based on the content of
our current page object.
-We then generate an edit URL because it's easier to do here than in the
+We then generate an edit URL, because it's easier to do here than in the
template, and we return a dictionary with a number of arguments. The fact that
``view_page()`` returns a dictionary (as opposed to a :term:`response` object)
is a cue to :app:`Pyramid` that it should try to use a :term:`renderer`
associated with the view configuration to render a response. In our case, the
-renderer used will be the ``templates/view.pt`` template, as indicated in the
-``@view_config`` decorator that is applied to ``view_page()``.
+renderer used will be the ``view.jinja2`` template, as indicated in
+the ``@view_config`` decorator that is applied to ``view_page()``.
-The ``add_page`` view function
-------------------------------
+If the page does not exist, then we need to handle that by raising a
+:class:`pyramid.httpexceptions.HTTPNotFound` to trigger our 404 handling,
+defined in ``tutorial/views/notfound.py``.
-Here is the code for the ``add_page`` view function and its decorator:
-
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 47-58
- :lineno-start: 47
- :linenos:
- :language: python
-
-``add_page()`` is invoked when a user clicks on a *WikiWord* which
-isn't yet represented as a page in the system. The ``check`` function
-within the ``view_page`` view generates URLs to this view.
-``add_page()`` also acts as a handler for the form that is generated
-when we want to add a page object. The ``matchdict`` attribute of the
-request passed to the ``add_page()`` view will have the values we need
-to construct URLs and find model objects.
-
-The ``matchdict`` will have a ``'pagename'`` key that matches the name of
-the page we'd like to add. If our add view is invoked via,
-e.g., ``http://localhost:6543/add_page/SomeName``, the value for
-``'pagename'`` in the ``matchdict`` will be ``'SomeName'``.
+.. note::
-If the view execution *is* a result of a form submission (i.e., the expression
-``'form.submitted' in request.params`` is ``True``), we grab the page body
-from the form data, create a Page object with this page body and the name
-taken from ``matchdict['pagename']``, and save it into the database using
-``DBSession.add``. We then redirect back to the ``view_page`` view for the
-newly created page.
+ Using ``raise`` versus ``return`` with the HTTP exceptions is an important
+ distinction that can commonly mess people up. In
+ ``tutorial/views/notfound.py`` there is an :term:`exception view`
+ registered for handling the ``HTTPNotFound`` exception. Exception views are
+ only triggered for raised exceptions. If the ``HTTPNotFound`` is returned,
+ then it has an internal "stock" template that it will use to render itself
+ as a response. If you aren't seeing your exception view being executed, this
+ is most likely the problem! See :ref:`special_exceptions_in_callables` for
+ more information about exception views.
-If the view execution is *not* a result of a form submission (i.e., the
-expression ``'form.submitted' in request.params`` is ``False``), the view
-callable renders a template. To do so, it generates a ``save_url`` which the
-template uses as the form post URL during rendering. We're lazy here, so
-we're going to use the same template (``templates/edit.pt``) for the add
-view as well as the page edit view. To do so we create a dummy Page object
-in order to satisfy the edit form's desire to have *some* page object
-exposed as ``page``. :app:`Pyramid` will render the template associated
-with this view to a response.
The ``edit_page`` view function
-------------------------------
Here is the code for the ``edit_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 60-72
- :lineno-start: 60
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 44-56
+ :lineno-match:
:linenos:
:language: python
-``edit_page()`` is invoked when a user clicks the "Edit this
-Page" button on the view form. It renders an edit form but it also acts as
-the handler for the form it renders. The ``matchdict`` attribute of the
-request passed to the ``edit_page`` view will have a ``'pagename'`` key
-matching the name of the page the user wants to edit.
+``edit_page()`` is invoked when a user clicks the "Edit this Page" button on
+the view form. It renders an edit form, but it also acts as the handler for the
+form which it renders. The ``matchdict`` attribute of the request passed to the
+``edit_page`` view will have a ``'pagename'`` key matching the name of the page
+that the user wants to edit.
If the view execution *is* a result of a form submission (i.e., the expression
``'form.submitted' in request.params`` is ``True``), the view grabs the
@@ -230,120 +263,190 @@ expression ``'form.submitted' in request.params`` is ``False``), the view
simply renders the edit form, passing the page object and a ``save_url``
which will be used as the action of the generated form.
+.. note::
+
+ Since our ``request.dbsession`` defined in the previous chapter is
+ registered with the ``pyramid_tm`` transaction manager, any changes we make
+ to objects managed by the that session will be committed automatically. In
+ the event that there was an error (even later, in our template code), the
+ changes would be aborted. This means the view itself does not need to
+ concern itself with commit/rollback logic.
+
+
+The ``add_page`` view function
+------------------------------
+
+Here is the code for the ``add_page`` view function and its decorator:
+
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 58-
+ :lineno-match:
+ :linenos:
+ :language: python
+
+``add_page()`` is invoked when a user clicks on a *WikiWord* which isn't yet
+represented as a page in the system. The ``add_link`` function within the
+``view_page`` view generates URLs to this view. ``add_page()`` also acts as a
+handler for the form that is generated when we want to add a page object. The
+``matchdict`` attribute of the request passed to the ``add_page()`` view will
+have the values we need to construct URLs and find model objects.
+
+The ``matchdict`` will have a ``'pagename'`` key that matches the name of the
+page we'd like to add. If our add view is invoked via, for example,
+``http://localhost:6543/add_page/SomeName``, the value for ``'pagename'`` in
+the ``matchdict`` will be ``'SomeName'``.
+
+Next a check is performed to determine whether the ``Page`` already exists in
+the database. If it already exists, then the client is redirected to the
+``edit_page`` view, else we continue to the next check.
+
+If the view execution *is* a result of a form submission (i.e., the expression
+``'form.submitted' in request.params`` is ``True``), we grab the page body from
+the form data, create a Page object with this page body and the name taken from
+``matchdict['pagename']``, and save it into the database using
+``request.dbession.add``. Since we have not yet covered authentication, we
+don't have a logged-in user to add as the page's ``creator``. Until we get to
+that point in the tutorial, we'll just assume that all pages are created by the
+``editor`` user. Thus we query for that object, and set it on ``page.creator``.
+Finally, we redirect the client back to the ``view_page`` view for the newly
+created page.
+
+If the view execution is *not* a result of a form submission (i.e., the
+expression ``'form.submitted' in request.params`` is ``False``), the view
+callable renders a template. To do so, it generates a ``save_url`` which the
+template uses as the form post URL during rendering. We're lazy here, so
+we're going to use the same template (``templates/edit.jinja2``) for the add
+view as well as the page edit view. To do so we create a dummy ``Page`` object
+in order to satisfy the edit form's desire to have *some* page object
+exposed as ``page``. :app:`Pyramid` will render the template associated
+with this view to a response.
+
+
Adding templates
================
The ``view_page``, ``add_page`` and ``edit_page`` views that we've added
-reference a :term:`template`. Each template is a :term:`Chameleon`
-:term:`ZPT` template. These templates will live in the ``templates``
-directory of our tutorial package. Chameleon templates must have a ``.pt``
-extension to be recognized as such.
+reference a :term:`template`. Each template is a :term:`Jinja2` template.
+These templates will live in the ``templates`` directory of our tutorial
+package. Jinja2 templates must have a ``.jinja2`` extension to be recognized
+as such.
+
-The ``view.pt`` template
-------------------------
+The ``layout.jinja2`` template
+------------------------------
-Create ``tutorial/tutorial/templates/view.pt`` and add the following
-content:
+Update ``tutorial/templates/layout.jinja2`` with the following content, as
+indicated by the emphasized lines:
-.. literalinclude:: src/views/tutorial/templates/view.pt
+.. literalinclude:: src/views/tutorial/templates/layout.jinja2
:linenos:
+ :emphasize-lines: 11,35-36
:language: html
-This template is used by ``view_page()`` for displaying a single
-wiki page. It includes:
+Since we're using a templating engine, we can factor common boilerplate out of
+our page templates into reusable components. One method for doing this is
+template inheritance via blocks.
-- A ``div`` element that is replaced with the ``content`` value provided by
- the view (lines 36-38). ``content`` contains HTML, so the ``structure``
- keyword is used to prevent escaping it (i.e., changing ">" to "&gt;", etc.)
-- A link that points at the "edit" URL which invokes the ``edit_page`` view
- for the page being viewed (lines 40-42).
+- We have defined two placeholders in the layout template where a child
+ template can override the content. These blocks are named ``subtitle`` (line
+ 11) and ``content`` (line 36).
+- Please refer to the Jinja2_ documentation for more information about template
+ inheritance.
-The ``edit.pt`` template
-------------------------
-Create ``tutorial/tutorial/templates/edit.pt`` and add the following
-content:
+The ``view.jinja2`` template
+----------------------------
-.. literalinclude:: src/views/tutorial/templates/edit.pt
+Create ``tutorial/templates/view.jinja2`` and add the following content:
+
+.. literalinclude:: src/views/tutorial/templates/view.jinja2
:linenos:
:language: html
-This template is used by ``add_page()`` and ``edit_page()`` for adding and
-editing a wiki page. It displays a page containing a form that includes:
+This template is used by ``view_page()`` for displaying a single wiki page.
-- A 10 row by 60 column ``textarea`` field named ``body`` that is filled
- with any existing page data when it is rendered (line 45).
-- A submit button that has the name ``form.submitted`` (line 48).
+- We begin by extending the ``layout.jinja2`` template defined above, which
+ provides the skeleton of the page (line 1).
+- We override the ``subtitle`` block from the base layout, inserting the page
+ name into the page's title (line 3).
+- We override the ``content`` block from the base layout to insert our markup
+ into the body (lines 5-18).
+- We use a variable that is replaced with the ``content`` value provided by the
+ view (line 6). ``content`` contains HTML, so the ``|safe`` filter is used to
+ prevent escaping it (e.g., changing ">" to "&gt;").
+- We create a link that points at the "edit" URL, which when clicked invokes
+ the ``edit_page`` view for the requested page (line 9).
-The form POSTs back to the ``save_url`` argument supplied by the view (line
-43). The view will use the ``body`` and ``form.submitted`` values.
-.. note:: Our templates use a ``request`` object that none of our tutorial
- views return in their dictionary. ``request`` is one of several names that
- are available "by default" in a template when a template renderer is used.
- See :ref:`renderer_system_values` for information about other names that
- are available by default when a template is used as a renderer.
+The ``edit.jinja2`` template
+----------------------------
-Static Assets
--------------
+Create ``tutorial/templates/edit.jinja2`` and add the following content:
-Our templates name static assets, including CSS and images. We don't need
-to create these files within our package's ``static`` directory because they
-were provided at the time we created the project.
+.. literalinclude:: src/views/tutorial/templates/edit.jinja2
+ :linenos:
+ :emphasize-lines: 1,3,12,14,17
+ :language: html
-As an example, the CSS file will be accessed via
-``http://localhost:6543/static/theme.css`` by virtue of the call to the
-``add_static_view`` directive we've made in the ``__init__.py`` file. Any
-number and type of static assets can be placed in this directory (or
-subdirectories) and are just referred to by URL or by using the convenience
-method ``static_url``, e.g.,
-``request.static_url('<package>:static/foo.css')`` within templates.
-
-Adding Routes to ``__init__.py``
-================================
-
-The ``__init__.py`` file contains
-:meth:`pyramid.config.Configurator.add_route` calls which serve to add routes
-to our application. First, we’ll get rid of the existing route created by
-the template using the name ``'home'``. It’s only an example and isn’t
-relevant to our application.
-
-We then need to add four calls to ``add_route``. Note that the *ordering* of
-these declarations is very important. ``route`` declarations are matched in
-the order they're found in the ``__init__.py`` file.
-
-#. Add a declaration which maps the pattern ``/`` (signifying the root URL)
- to the route named ``view_wiki``. It maps to our ``view_wiki`` view
- callable by virtue of the ``@view_config`` attached to the ``view_wiki``
- view function indicating ``route_name='view_wiki'``.
+This template serves two use cases. It is used by ``add_page()`` and
+``edit_page()`` for adding and editing a wiki page. It displays a page
+containing a form and which provides the following:
-#. Add a declaration which maps the pattern ``/{pagename}`` to the route named
- ``view_page``. This is the regular view for a page. It maps
- to our ``view_page`` view callable by virtue of the ``@view_config``
- attached to the ``view_page`` view function indicating
- ``route_name='view_page'``.
+- Again, we extend the ``layout.jinja2`` template, which provides the skeleton
+ of the page (line 1).
+- Override the ``subtitle`` block to affect the ``<title>`` tag in the
+ ``head`` of the page (line 3).
+- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with
+ any existing page data when it is rendered (line 14).
+- A submit button that has the name ``form.submitted`` (line 17).
+- The form POSTs back to the ``save_url`` argument supplied by the view (line
+ 12). The view will use the ``body`` and ``form.submitted`` values.
-#. Add a declaration which maps the pattern ``/add_page/{pagename}`` to the
- route named ``add_page``. This is the add view for a new page. It maps
- to our ``add_page`` view callable by virtue of the ``@view_config``
- attached to the ``add_page`` view function indicating
- ``route_name='add_page'``.
-#. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the
- route named ``edit_page``. This is the edit view for a page. It maps
- to our ``edit_page`` view callable by virtue of the ``@view_config``
- attached to the ``edit_page`` view function indicating
- ``route_name='edit_page'``.
+The ``404.jinja2`` template
+---------------------------
+
+Replace ``tutorial/templates/404.jinja2`` with the following content:
+
+.. literalinclude:: src/views/tutorial/templates/404.jinja2
+ :linenos:
+ :language: html
-As a result of our edits, the ``__init__.py`` file should look
-something like:
+This template is linked from the ``notfound_view`` defined in
+``tutorial/views/notfound.py`` as shown here:
-.. literalinclude:: src/views/tutorial/__init__.py
+.. literalinclude:: src/views/tutorial/views/notfound.py
:linenos:
- :emphasize-lines: 19-22
+ :emphasize-lines: 6
:language: python
-The highlighted lines are the ones that need to be added or edited.
+There are several important things to note about this configuration:
+
+- The ``notfound_view`` in the above snippet is called an
+ :term:`exception view`. For more information see
+ :ref:`special_exceptions_in_callables`.
+- The ``notfound_view`` sets the response status to 404. It's possible
+ to affect the response object used by the renderer via
+ :ref:`request_response_attr`.
+- The ``notfound_view`` is registered as an exception view and will be invoked
+ **only** if ``pyramid.httpexceptions.HTTPNotFound`` is raised as an
+ exception. This means it will not be invoked for any responses returned
+ from a view normally. For example, on line 27 of
+ ``tutorial/views/default.py`` the exception is raised which will trigger
+ the view.
+
+Finally, we may delete the ``tutorial/templates/mytemplate.jinja2`` template
+that was provided by the ``alchemy`` scaffold, as we have created our own
+templates for the wiki.
+
+.. note::
+
+ Our templates use a ``request`` object that none of our tutorial
+ views return in their dictionary. ``request`` is one of several names that
+ are available "by default" in a template when a template renderer is used.
+ See :ref:`renderer_system_values` for information about other names that
+ are available by default when a template is used as a renderer.
+
Viewing the application in a browser
====================================
@@ -355,15 +458,22 @@ each of the following URLs, checking that the result is as expected:
- http://localhost:6543/ invokes the ``view_wiki`` view. This always
redirects to the ``view_page`` view of the ``FrontPage`` page object.
-- http://localhost:6543/FrontPage invokes the ``view_page`` view of the front
- page object.
+- http://localhost:6543/FrontPage invokes the ``view_page`` view of the
+ ``FrontPage`` page object.
+
+- http://localhost:6543/FrontPage/edit_page invokes the ``edit_page`` view for
+ the ``FrontPage`` page object.
-- http://localhost:6543/FrontPage/edit_page invokes the edit view for the
- front page object.
+- http://localhost:6543/add_page/SomePageName invokes the ``add_page`` view for
+ a page. If the page already exists, then it redirects the user to the
+ ``edit_page`` view for the page object.
-- http://localhost:6543/add_page/SomePageName invokes the add view for a page.
+- http://localhost:6543/SomePageName/edit_page invokes the ``edit_page`` view
+ for an existing page, or generates an error if the page does not exist.
- To generate an error, visit http://localhost:6543/foobars/edit_page which
will generate a ``NoResultFound: No row was found for one()`` error. You'll
see an interactive traceback facility provided by
:term:`pyramid_debugtoolbar`.
+
+.. _jinja2: http://jinja.pocoo.org/
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index e9f361e7d..523a6e6d8 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -1,150 +1,162 @@
-==========
+.. _wiki2_design:
+
+======
Design
-==========
+======
-Following is a quick overview of the design of our wiki application, to help
-us understand the changes that we will be making as we work through the
-tutorial.
+Following is a quick overview of the design of our wiki application to help us
+understand the changes that we will be making as we work through the tutorial.
Overall
--------
+=======
We choose to use :term:`reStructuredText` markup in the wiki text. Translation
from reStructuredText to HTML is provided by the widely used ``docutils``
-Python module. We will add this module in the dependency list on the project
+Python module. We will add this module to the dependency list in the project's
``setup.py`` file.
Models
-------
+======
-We'll be using a SQLite database to hold our wiki data, and we'll be using
+We'll be using an SQLite database to hold our wiki data, and we'll be using
:term:`SQLAlchemy` to access the data in this database.
-Within the database, we define a single table named `pages`, whose elements
-will store the wiki pages. There are two columns: `name` and `data`.
+Within the database, we will define two tables:
+
+- The `users` table which will store the `id`, `name`, `password_hash` and
+ `role` of each wiki user.
+- The `pages` table, whose elements will store the wiki pages.
+ There are four columns: `id`, `name`, `data` and `creator_id`.
-URLs like ``/PageName`` will try to find an element in
-the table that has a corresponding name.
+There is a one-to-many relationship between `users` and `pages` tracking
+the user who created each wiki page defined by the `creator_id` column on the
+`pages` table.
-To add a page to the wiki, a new row is created and the text
-is stored in `data`.
+URLs like ``/PageName`` will try to find an element in the `pages` table that
+has a corresponding name.
+
+To add a page to the wiki, a new row is created and the text is stored in
+`data`.
A page named ``FrontPage`` containing the text *This is the front page*, will
be created when the storage is initialized, and will be used as the wiki home
page.
-Views
------
+Wiki Views
+==========
-There will be three views to handle the normal operations of adding,
-editing, and viewing wiki pages, plus one view for the wiki front page.
-Two templates will be used, one for viewing, and one for both adding
-and editing wiki pages.
+There will be three views to handle the normal operations of adding, editing,
+and viewing wiki pages, plus one view for the wiki front page. Two templates
+will be used, one for viewing, and one for both adding and editing wiki pages.
-The default templating systems in :app:`Pyramid` are
-:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of
-:term:`ZPT`, which is an XML-based templating language. Mako is a
-non-XML-based templating language. Because we had to pick one,
-we chose Chameleon for this tutorial.
+As of version 1.5 :app:`Pyramid` no longer ships with templating systems. In
+this tutorial, we will use :term:`Jinja2`. Jinja2 is a modern and
+designer-friendly templating language for Python, modeled after Django's
+templates.
Security
---------
-
-We'll eventually be adding security to our application. The components we'll
-use to do this are below.
-
-- USERS, a dictionary mapping :term:`userids <userid>` to their
- corresponding passwords.
-
-- GROUPS, a dictionary mapping :term:`userids <userid>` to a
- list of groups to which they belong.
-
-- ``groupfinder``, an *authorization callback* that looks up USERS and
- GROUPS. It will be provided in a new ``security.py`` file.
-
-- An :term:`ACL` is attached to the root :term:`resource`. Each row below
- details an :term:`ACE`:
-
- +----------+----------------+----------------+
- | Action | Principal | Permission |
- +==========+================+================+
- | Allow | Everyone | View |
- +----------+----------------+----------------+
- | Allow | group:editors | Edit |
- +----------+----------------+----------------+
-
-- Permission declarations are added to the views to assert the security
- policies as each request is handled.
-
-Two additional views and one template will handle the login and
-logout tasks.
+========
+
+We'll eventually be adding security to our application. To do this, we'll
+be using a very simple role-based security model. We'll assign a single
+role category to each user in our system.
+
+`basic`
+ An authenticated user who can view content and create new pages. A `basic`
+ user may also edit the pages they have created but not pages created by
+ other users.
+
+`editor`
+ An authenticated user who can create and edit any content in the system.
+
+In order to accomplish this we'll need to define an authentication policy
+which can identify users by their :term:`userid` and role. Then we'll
+need to define a page :term:`resource` which contains the appropriate
+:term:`ACL`:
+
++----------+--------------------+----------------+
+| Action | Principal | Permission |
++==========+====================+================+
+| Allow | Everyone | view |
++----------+--------------------+----------------+
+| Allow | group:basic | create |
++----------+--------------------+----------------+
+| Allow | group:editors | edit |
++----------+--------------------+----------------+
+| Allow | <creator of page> | edit |
++----------+--------------------+----------------+
+
+Permission declarations will be added to the views to assert the security
+policies as each request is handled.
+
+On the security side of the application there are two additional views for
+handling login and logout as well as two exception views for handling
+invalid access attempts and unhandled URLs.
Summary
--------
-
-The URL, actions, template and permission associated to each view are
-listed in the following table:
-
-+----------------------+-----------------------+-------------+------------+------------+
-| URL | Action | View | Template | Permission |
-| | | | | |
-+======================+=======================+=============+============+============+
-| / | Redirect to | view_wiki | | |
-| | /FrontPage | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /PageName | Display existing | view_page | view.pt | view |
-| | page [2]_ | [1]_ | | |
-| | | | | |
-| | | | | |
-| | | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /PageName/edit_page | Display edit form | edit_page | edit.pt | edit |
-| | with existing | | | |
-| | content. | | | |
-| | | | | |
-| | If the form was | | | |
-| | submitted, redirect | | | |
-| | to /PageName | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /add_page/PageName | Create the page | add_page | edit.pt | edit |
-| | *PageName* in | | | |
-| | storage, display | | | |
-| | the edit form | | | |
-| | without content. | | | |
-| | | | | |
-| | If the form was | | | |
-| | submitted, | | | |
-| | redirect to | | | |
-| | /PageName | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /login | Display login form, | login | login.pt | |
-| | Forbidden [3]_ | | | |
-| | | | | |
-| | If the form was | | | |
-| | submitted, | | | |
-| | authenticate. | | | |
-| | | | | |
-| | - If authentication | | | |
-| | succeeds, | | | |
-| | redirect to the | | | |
-| | page that we | | | |
-| | came from. | | | |
-| | | | | |
-| | - If authentication | | | |
-| | fails, display | | | |
-| | login form with | | | |
-| | "login failed" | | | |
-| | message. | | | |
-| | | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /logout | Redirect to | logout | | |
-| | /FrontPage | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-
-.. [1] This is the default view for a Page context
- when there is no view name.
-.. [2] Pyramid will return a default 404 Not Found page
- if the page *PageName* does not exist yet.
-.. [3] ``pyramid.exceptions.Forbidden`` is reached when a
- user tries to invoke a view that is
- not authorized by the authorization policy.
+=======
+
+The URL, actions, template, and permission associated to each view are listed
+in the following table:
+
++----------------------+-----------------------+-------------+----------------+------------+
+| URL | Action | View | Template | Permission |
+| | | | | |
++======================+=======================+=============+================+============+
+| / | Redirect to | view_wiki | | |
+| | /FrontPage | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /PageName | Display existing | view_page | view.jinja2 | view |
+| | page [2]_ | [1]_ | | |
+| | | | | |
+| | | | | |
+| | | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /PageName/edit_page | Display edit form | edit_page | edit.jinja2 | edit |
+| | with existing | | | |
+| | content. | | | |
+| | | | | |
+| | If the form was | | | |
+| | submitted, redirect | | | |
+| | to /PageName | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /add_page/PageName | Create the page | add_page | edit.jinja2 | create |
+| | *PageName* in | | | |
+| | storage, display | | | |
+| | the edit form | | | |
+| | without content. | | | |
+| | | | | |
+| | If the form was | | | |
+| | submitted, | | | |
+| | redirect to | | | |
+| | /PageName | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /login | Display login form, | login | login.jinja2 | |
+| | Forbidden [3]_ | | | |
+| | | | | |
+| | If the form was | | | |
+| | submitted, | | | |
+| | authenticate. | | | |
+| | | | | |
+| | - If authentication | | | |
+| | succeeds, | | | |
+| | redirect to the | | | |
+| | page from which | | | |
+| | we came. | | | |
+| | | | | |
+| | - If authentication | | | |
+| | fails, display | | | |
+| | login form with | | | |
+| | "login failed" | | | |
+| | message. | | | |
+| | | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /logout | Redirect to | logout | | |
+| | /FrontPage | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+
+.. [1] This is the default view for a Page context when there is no view name.
+.. [2] Pyramid will return a default 404 Not Found page if the page *PageName*
+ does not exist yet.
+.. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke
+ a view that is not authorized by the authorization policy.
diff --git a/docs/tutorials/wiki2/distributing.rst b/docs/tutorials/wiki2/distributing.rst
index fee50a1cf..f38a733f4 100644
--- a/docs/tutorials/wiki2/distributing.rst
+++ b/docs/tutorials/wiki2/distributing.rst
@@ -1,22 +1,23 @@
+.. _wiki2_distributing_your_application:
+
=============================
Distributing Your Application
=============================
Once your application works properly, you can create a "tarball" from it by
using the ``setup.py sdist`` command. The following commands assume your
-current working directory is the ``tutorial`` package we've created and that
-the parent directory of the ``tutorial`` package is a virtualenv representing
-a :app:`Pyramid` environment.
+current working directory contains the ``tutorial`` package and the
+``setup.py`` file.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/python setup.py sdist
On Windows:
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut> %VENV%\Scripts\python setup.py sdist
@@ -27,14 +28,13 @@ The output of such a command will be something like:
running sdist
# .. more output ..
creating dist
- tar -cf dist/tutorial-0.0.tar tutorial-0.0
- gzip -f9 dist/tutorial-0.0.tar
+ Creating tar archive
removing 'tutorial-0.0' (and everything under it)
Note that this command creates a tarball in the "dist" subdirectory named
``tutorial-0.0.tar.gz``. You can send this file to your friends to show them
your cool new application. They should be able to install it by pointing the
``easy_install`` command directly at it. Or you can upload it to `PyPI
-<http://pypi.python.org>`_ and share it with the rest of the world, where it
-can be downloaded via ``easy_install`` remotely like any other package people
-download from PyPI.
+<https://pypi.python.org/pypi>`_ and share it with the rest of the world, where
+it can be downloaded via ``easy_install`` remotely like any other package
+people download from PyPI.
diff --git a/docs/tutorials/wiki2/index.rst b/docs/tutorials/wiki2/index.rst
index 0a3873dcd..18e9f552e 100644
--- a/docs/tutorials/wiki2/index.rst
+++ b/docs/tutorials/wiki2/index.rst
@@ -1,15 +1,15 @@
.. _bfg_sql_wiki_tutorial:
-SQLAlchemy + URL Dispatch Wiki Tutorial
+SQLAlchemy + URL dispatch wiki tutorial
=======================================
-This tutorial introduces a :term:`SQLAlchemy` and :term:`url dispatch`-based
+This tutorial introduces an :term:`SQLAlchemy` and :term:`URL dispatch`-based
:app:`Pyramid` application to a developer familiar with Python. When the
-tutorial is finished, the developer will have created a basic Wiki
-application with authentication.
+tutorial is finished, the developer will have created a basic wiki
+application with authentication and authorization.
-For cut and paste purposes, the source code for all stages of this
-tutorial can be browsed on GitHub at `docs/tutorials/wiki2/src
+For cut and paste purposes, the source code for all stages of this tutorial can
+be browsed on GitHub at `docs/tutorials/wiki2/src
<https://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src>`_,
which corresponds to the same location if you have Pyramid sources.
@@ -22,6 +22,7 @@ which corresponds to the same location if you have Pyramid sources.
basiclayout
definingmodels
definingviews
+ authentication
authorization
tests
distributing
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index 1385ab8c7..a214b1306 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -1,17 +1,19 @@
+.. _wiki2_installation:
+
============
Installation
============
Before you begin
-================
+----------------
This tutorial assumes that you have already followed the steps in
-:ref:`installing_chapter`, except **do not create a virtualenv or install
-Pyramid**. Thereby you will satisfy the following requirements.
+:ref:`installing_chapter`, except **do not create a virtual environment or
+install Pyramid**. Thereby you will satisfy the following requirements.
+
+* A Python interpreter is installed on your operating system.
+* You've satisfied the :ref:`requirements-for-installing-packages`.
-* Python interpreter is installed on your operating system
-* :term:`setuptools` or :term:`distribute` is installed
-* :term:`virtualenv` is installed
Create directory to contain the project
---------------------------------------
@@ -21,55 +23,73 @@ We need a workspace for our project files.
On UNIX
^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ mkdir ~/pyramidtut
On Windows
^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\> mkdir pyramidtut
+
Create and use a virtual Python environment
-------------------------------------------
-Next let's create a `virtualenv` workspace for our project. We will
-use the `VENV` environment variable instead of the absolute path of the
-virtual environment.
+Next let's create a virtual environment workspace for our project. We will use
+the ``VENV`` environment variable instead of the absolute path of the virtual
+environment.
On UNIX
^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ export VENV=~/pyramidtut
- $ virtualenv $VENV
- New python executable in /home/foo/env/bin/python
- Installing setuptools.............done.
+ $ python3 -m venv $VENV
On Windows
^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\> set VENV=c:\pyramidtut
-Versions of Python use different paths, so you will need to adjust the
+Each version of Python uses different paths, so you will need to adjust the
path to the command for your Python version.
Python 2.7:
-.. code-block:: text
+.. code-block:: doscon
c:\> c:\Python27\Scripts\virtualenv %VENV%
-Python 3.2:
+Python 3.5:
-.. code-block:: text
+.. code-block:: doscon
+
+ c:\> c:\Python35\Scripts\python -m venv %VENV%
+
+
+Upgrade ``pip`` and ``setuptools`` in the virtual environment
+-------------------------------------------------------------
+
+On UNIX
+^^^^^^^
+
+.. code-block:: bash
+
+ $ $VENV/bin/pip install --upgrade pip setuptools
+
+On Windows
+^^^^^^^^^^
+
+.. code-block:: doscon
+
+ c:\> %VENV%\Scripts\pip install --upgrade pip setuptools
- c:\> c:\Python32\Scripts\virtualenv %VENV%
Install Pyramid into the virtual Python environment
---------------------------------------------------
@@ -77,16 +97,17 @@ Install Pyramid into the virtual Python environment
On UNIX
^^^^^^^
-.. code-block:: text
+.. parsed-literal::
- $ $VENV/bin/easy_install pyramid
+ $ $VENV/bin/pip install "pyramid==\ |release|\ "
On Windows
^^^^^^^^^^
-.. code-block:: text
+.. parsed-literal::
+
+ c:\\> %VENV%\\Scripts\\pip install "pyramid==\ |release|\ "
- c:\> %VENV%\Scripts\easy_install pyramid
Install SQLite3 and its development packages
--------------------------------------------
@@ -94,276 +115,368 @@ Install SQLite3 and its development packages
If you used a package manager to install your Python or if you compiled
your Python from source, then you must install SQLite3 and its
development packages. If you downloaded your Python as an installer
-from python.org, then you already have it installed and can proceed to
-the next section :ref:`sql_making_a_project`..
+from https://www.python.org, then you already have it installed and can skip
+this step.
If you need to install the SQLite3 packages, then, for example, using
-the Debian system and apt-get, the command would be the following:
+the Debian system and ``apt-get``, the command would be the following:
-.. code-block:: text
+.. code-block:: bash
$ sudo apt-get install libsqlite3-dev
+
Change directory to your virtual Python environment
---------------------------------------------------
-Change directory to the ``pyramidtut`` directory.
+Change directory to the ``pyramidtut`` directory, which is both your workspace
+and your virtual environment.
On UNIX
^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ cd pyramidtut
On Windows
^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\> cd pyramidtut
+
.. _sql_making_a_project:
Making a project
-================
+----------------
Your next step is to create a project. For this tutorial we will use
the :term:`scaffold` named ``alchemy`` which generates an application
that uses :term:`SQLAlchemy` and :term:`URL dispatch`.
-:app:`Pyramid` supplies a variety of scaffolds to generate sample
-projects. We will use `pcreate`—a script that comes with Pyramid to
-quickly and easily generate scaffolds, usually with a single command—to
-create the scaffold for our project.
+:app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We
+will use ``pcreate``, a script that comes with Pyramid, to create our project
+using a scaffold.
-By passing `alchemy` into the `pcreate` command, the script creates
-the files needed to use SQLAlchemy. By passing in our application name
-`tutorial`, the script inserts that application name into all the
-required files. For example, `pcreate` creates the
-``initialize_tutorial_db`` in the ``pyramidtut/bin`` directory.
+By passing ``alchemy`` into the ``pcreate`` command, the script creates the
+files needed to use SQLAlchemy. By passing in our application name
+``tutorial``, the script inserts that application name into all the required
+files. For example, ``pcreate`` creates the ``initialize_tutorial_db`` in the
+``pyramidtut/bin`` directory.
The below instructions assume your current working directory is "pyramidtut".
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/pcreate -s alchemy tutorial
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut> %VENV%\Scripts\pcreate -s alchemy tutorial
-.. note:: If you are using Windows, the ``alchemy``
- scaffold may not deal gracefully with installation into a
- location that contains spaces in the path. If you experience
- startup problems, try putting both the virtualenv and the project
- into directories that do not contain spaces in their paths.
+.. note:: If you are using Windows, the ``alchemy`` scaffold may not deal
+ gracefully with installation into a location that contains spaces in the
+ path. If you experience startup problems, try putting both the virtual
+ environment and the project into directories that do not contain spaces in
+ their paths.
+
.. _installing_project_in_dev_mode:
Installing the project in development mode
-==========================================
+------------------------------------------
-In order to do development on the project easily, you must "register"
-the project as a development egg in your workspace using the
-``setup.py develop`` command. In order to do so, cd to the `tutorial`
-directory you created in :ref:`sql_making_a_project`, and run the
-``setup.py develop`` command using the virtualenv Python interpreter.
+In order to do development on the project easily, you must "register" the
+project as a development egg in your workspace using the ``pip install -e .``
+command. In order to do so, change directory to the ``tutorial`` directory that
+you created in :ref:`sql_making_a_project`, and run the ``pip install -e .``
+command using the virtual environment Python interpreter.
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ cd tutorial
- $ $VENV/bin/python setup.py develop
+ $ $VENV/bin/pip install -e .
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut> cd tutorial
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e .
+
+The console will show ``pip`` checking for packages and installing missing
+packages. Success executing this command will show a line like the following:
+
+.. code-block:: bash
+
+ Successfully installed Chameleon-2.24 Mako-1.0.4 MarkupSafe-0.23 \
+ Pygments-2.1.3 SQLAlchemy-1.0.12 pyramid-chameleon-0.3 \
+ pyramid-debugtoolbar-2.4.2 pyramid-mako-1.0.2 pyramid-tm-0.12.1 \
+ transaction-1.4.4 tutorial waitress-0.8.10 zope.sqlalchemy-0.7.6
+
+
+.. _install-testing-requirements:
+
+Install testing requirements
+----------------------------
+
+In order to run tests, we need to install the testing requirements. This is
+done through our project's ``setup.py`` file, in the ``tests_require`` and
+``extras_require`` stanzas, and by issuing the command below for your
+operating system.
+
+.. literalinclude:: src/installation/setup.py
+ :language: python
+ :linenos:
+ :lineno-start: 22
+ :lines: 22-26
+
+.. literalinclude:: src/installation/setup.py
+ :language: python
+ :linenos:
+ :lineno-start: 45
+ :lines: 45-47
+
+On UNIX
+^^^^^^^
+
+.. code-block:: bash
-The console will show `setup.py` checking for packages and installing
-missing packages. Success executing this command will show a line like
-the following::
+ $ $VENV/bin/pip install -e ".[testing]"
+
+On Windows
+^^^^^^^^^^
+
+.. code-block:: doscon
+
+ c:\pyramidtut\tutorial> %VENV%\Scripts\pip install -e ".[testing]"
- Finished processing dependencies for tutorial==0.0
.. _sql_running_tests:
Run the tests
-=============
+-------------
-After you've installed the project in development mode, you may run
-the tests for the project.
+After you've installed the project in development mode as well as the testing
+requirements, you may run the tests for the project. The following commands
+provide options to py.test that specify the module for which its tests shall be
+run, and to run py.test in quiet mode.
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/python setup.py test -q
+ $ $VENV/bin/py.test -q
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
+
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py test -q
+For a successful test run, you should see output that ends like this:
-For a successful test run, you should see output that ends like this::
+.. code-block:: bash
+
+ ..
+ 2 passed in 0.44 seconds
- .
- ----------------------------------------------------------------------
- Ran 1 test in 0.094s
-
- OK
Expose test coverage information
-================================
+--------------------------------
-You can run the ``nosetests`` command to see test coverage
-information. This runs the tests in the same way that ``setup.py
-test`` does but provides additional "coverage" information, exposing
-which lines of your project are "covered" (or not covered) by the
+You can run the ``py.test`` command to see test coverage information. This
+runs the tests in the same way that ``py.test`` does, but provides additional
+"coverage" information, exposing which lines of your project are covered by the
tests.
-To get this functionality working, we'll need to install the ``nose`` and
-``coverage`` packages into our ``virtualenv``:
+We've already installed the ``pytest-cov`` package into our virtual
+environment, so we can run the tests with coverage.
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/easy_install nose coverage
+ $ $VENV/bin/py.test --cov --cov-report=term-missing
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
+
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov \
+ --cov-report=term-missing
+
+If successful, you will see output something like this:
+
+.. code-block:: bash
+
+ ======================== test session starts ========================
+ platform Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1
+ rootdir: /Users/stevepiercy/projects/pyramidtut/tutorial, inifile:
+ plugins: cov-2.2.1
+ collected 2 items
+
+ tutorial/tests.py ..
+ ------------------ coverage: platform Python 3.5.1 ------------------
+ Name Stmts Miss Cover Missing
+ ----------------------------------------------------------------
+ tutorial/__init__.py 8 6 25% 7-12
+ tutorial/models/__init__.py 22 0 100%
+ tutorial/models/meta.py 5 0 100%
+ tutorial/models/mymodel.py 8 0 100%
+ tutorial/routes.py 3 2 33% 2-3
+ tutorial/scripts/__init__.py 0 0 100%
+ tutorial/scripts/initializedb.py 26 16 38% 22-25, 29-45
+ tutorial/views/__init__.py 0 0 100%
+ tutorial/views/default.py 12 0 100%
+ tutorial/views/notfound.py 4 2 50% 6-7
+ ----------------------------------------------------------------
+ TOTAL 88 26 70%
+ ===================== 2 passed in 0.57 seconds ======================
- c:\pyramidtut\tutorial> %VENV%\Scripts\easy_install nose coverage
+Our package doesn't quite have 100% test coverage.
-Once ``nose`` and ``coverage`` are installed, we can actually run the
-coverage tests.
+
+.. _test_and_coverage_scaffold_defaults_sql:
+
+Test and coverage scaffold defaults
+-----------------------------------
+
+Scaffolds include configuration defaults for ``py.test`` and test coverage.
+These configuration files are ``pytest.ini`` and ``.coveragerc``, located at
+the root of your package. Without these defaults, we would need to specify the
+path to the module on which we want to run tests and coverage.
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/nosetests --cover-package=tutorial --cover-erase --with-coverage
+ $ $VENV/bin/py.test --cov=tutorial tutorial/tests.py -q
On Windows
-----------
-
-.. code-block:: text
+^^^^^^^^^^
- c:\pyramidtut\tutorial> %VENV%\Scripts\nosetests --cover-package=tutorial \
- --cover-erase --with-coverage
+.. code-block:: doscon
-If successful, you will see output something like this::
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test --cov=tutorial \
+ --cov-report=term-missing tutorial\tests.py -q
- .
- Name Stmts Miss Cover Missing
- ---------------------------------------------------
- tutorial.py 13 9 31% 13-21
- tutorial/models.py 12 0 100%
- tutorial/scripts.py 0 0 100%
- tutorial/views.py 11 0 100%
- ---------------------------------------------------
- TOTAL 36 9 75%
- ----------------------------------------------------------------------
- Ran 2 tests in 0.643s
+py.test follows :ref:`conventions for Python test discovery
+<pytest:test discovery>`, and the configuration defaults from the scaffold
+tell ``py.test`` where to find the module on which we want to run tests and
+coverage.
- OK
+.. seealso:: See py.test's documentation for :ref:`pytest:usage` or invoke
+ ``py.test -h`` to see its full set of options.
-Looks like our package doesn't quite have 100% test coverage.
.. _initialize_db_wiki2:
Initializing the database
-=========================
+-------------------------
-We need to use the ``initialize_tutorial_db`` :term:`console
-script` to initialize our database.
+We need to use the ``initialize_tutorial_db`` :term:`console script` to
+initialize our database.
+
+.. note::
+
+ The ``initialize_tutorial_db`` command does not perform a migration, but
+ rather it simply creates missing tables and adds some dummy data. If you
+ already have a database, you should delete it before running
+ ``initialize_tutorial_db`` again.
+
+.. note::
+
+ The ``initialize_tutorial_db`` command is not performing a migration but
+ rather simply creating missing tables and adding some dummy data. If you
+ already have a database, you should delete it before running
+ ``initialize_tutorial_db`` again.
Type the following command, making sure you are still in the ``tutorial``
directory (the directory with a ``development.ini`` in it):
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/initialize_tutorial_db development.ini
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut\tutorial> %VENV%\Scripts\initialize_tutorial_db development.ini
-The output to your console should be something like this::
-
- 2015-05-23 16:49:49,609 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
- 2015-05-23 16:49:49,609 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
- 2015-05-23 16:49:49,610 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
- 2015-05-23 16:49:49,610 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
- 2015-05-23 16:49:49,610 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("models")
- 2015-05-23 16:49:49,610 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2015-05-23 16:49:49,612 INFO [sqlalchemy.engine.base.Engine:1097][MainThread]
- CREATE TABLE models (
- id INTEGER NOT NULL,
- name TEXT,
- value INTEGER,
- PRIMARY KEY (id)
- )
-
-
- 2015-05-23 16:49:49,612 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2015-05-23 16:49:49,613 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
- 2015-05-23 16:49:49,613 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE UNIQUE INDEX my_index ON models (name)
- 2015-05-23 16:49:49,613 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
- 2015-05-23 16:49:49,614 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
- 2015-05-23 16:49:49,616 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit)
- 2015-05-23 16:49:49,617 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO models (name, value) VALUES (?, ?)
- 2015-05-23 16:49:49,617 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('one', 1)
- 2015-05-23 16:49:49,618 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
-
-Success! You should now have a ``tutorial.sqlite`` file in your current working
-directory. This will be a SQLite database with a single table defined in it
+The output to your console should be something like this:
+
+.. code-block:: bash
+
+ 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
+ 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
+ 2016-05-22 04:03:28,888 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
+ 2016-05-22 04:03:28,889 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] ()
+ 2016-05-22 04:03:28,890 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("models")
+ 2016-05-22 04:03:28,890 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
+ 2016-05-22 04:03:28,892 INFO [sqlalchemy.engine.base.Engine:1097][MainThread]
+ CREATE TABLE models (
+ id INTEGER NOT NULL,
+ name TEXT,
+ value INTEGER,
+ CONSTRAINT pk_models PRIMARY KEY (id)
+ )
+
+
+ 2016-05-22 04:03:28,892 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
+ 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+ 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE UNIQUE INDEX my_index ON models (name)
+ 2016-05-22 04:03:28,893 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ()
+ 2016-05-22 04:03:28,894 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+ 2016-05-22 04:03:28,896 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit)
+ 2016-05-22 04:03:28,897 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO models (name, value) VALUES (?, ?)
+ 2016-05-22 04:03:28,897 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('one', 1)
+ 2016-05-22 04:03:28,898 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT
+
+Success! You should now have a ``tutorial.sqlite`` file in your current
+working directory. This is an SQLite database with a single table defined in it
(``models``).
.. _wiki2-start-the-application:
Start the application
-=====================
+---------------------
Start the application.
On UNIX
--------
+^^^^^^^
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/pserve development.ini --reload
On Windows
-----------
+^^^^^^^^^^
-.. code-block:: text
+.. code-block:: doscon
c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload
@@ -372,42 +485,61 @@ On Windows
Your OS firewall, if any, may pop up a dialog asking for authorization
to allow python to accept incoming network connections.
-If successful, you will see something like this on your console::
+If successful, you will see something like this on your console:
- Starting subprocess with file monitor
- Starting server in PID 8966.
- Starting HTTP server on http://0.0.0.0:6543
+.. code-block:: text
+
+ Starting subprocess with file monitor
+ Starting server in PID 82349.
+ serving on http://127.0.0.1:6543
This means the server is ready to accept requests.
+
Visit the application in a browser
-==================================
+----------------------------------
-In a browser, visit `http://localhost:6543/ <http://localhost:6543>`_. You
-will see the generated application's default page.
+In a browser, visit http://localhost:6543/. You will see the generated
+application's default page.
One thing you'll notice is the "debug toolbar" icon on right hand side of the
page. You can read more about the purpose of the icon at
:ref:`debug_toolbar`. It allows you to get information about your
application while you develop.
+
Decisions the ``alchemy`` scaffold has made for you
-=================================================================
+---------------------------------------------------
Creating a project using the ``alchemy`` scaffold makes the following
assumptions:
-- you are willing to use :term:`SQLAlchemy` as a database access tool
+- You are willing to use :term:`SQLAlchemy` as a database access tool.
+
+- You are willing to use :term:`URL dispatch` to map URLs to code.
-- you are willing to use :term:`URL dispatch` to map URLs to code
+- You want to use zope.sqlalchemy_, pyramid_tm_, and the transaction_ packages
+ to scope sessions to requests.
-- you want to use ``ZopeTransactionExtension`` and ``pyramid_tm`` to scope
- sessions to requests
+- You want to use pyramid_jinja2_ to render your templates. Different
+ templating engines can be used, but we had to choose one to make this
+ tutorial. See :ref:`available_template_system_bindings` for some options.
.. note::
:app:`Pyramid` supports any persistent storage mechanism (e.g., object
- database or filesystem files). It also supports an additional
- mechanism to map URLs to code (:term:`traversal`). However, for the
- purposes of this tutorial, we'll only be using URL dispatch and
- SQLAlchemy.
+ database or filesystem files). It also supports an additional mechanism to
+ map URLs to code (:term:`traversal`). However, for the purposes of this
+ tutorial, we'll only be using :term:`URL dispatch` and :term:`SQLAlchemy`.
+
+.. _pyramid_jinja2:
+ http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/
+
+.. _pyramid_tm:
+ http://docs.pylonsproject.org/projects/pyramid-tm/en/latest/
+
+.. _zope.sqlalchemy:
+ https://pypi.python.org/pypi/zope.sqlalchemy
+
+.. _transaction:
+ http://zodb.readthedocs.org/en/latest/transactions.html
diff --git a/docs/tutorials/wiki2/src/authentication/CHANGES.txt b/docs/tutorials/wiki2/src/authentication/CHANGES.txt
new file mode 100644
index 000000000..35a34f332
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version
diff --git a/docs/tutorials/wiki2/src/authentication/MANIFEST.in b/docs/tutorials/wiki2/src/authentication/MANIFEST.in
new file mode 100644
index 000000000..42cd299b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.txt *.ini *.cfg *.rst
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki2/src/authentication/README.txt b/docs/tutorials/wiki2/src/authentication/README.txt
new file mode 100644
index 000000000..5b0101e5f
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/README.txt
@@ -0,0 +1,14 @@
+tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/initialize_tutorial_db development.ini
+
+- $VENV/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki2/src/authentication/development.ini b/docs/tutorials/wiki2/src/authentication/development.ini
new file mode 100644
index 000000000..4a6c9325c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/development.ini
@@ -0,0 +1,73 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_tm
+
+sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+
+auth.secret = seekrit
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+host = 127.0.0.1
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial, sqlalchemy
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[logger_sqlalchemy]
+level = INFO
+handlers =
+qualname = sqlalchemy.engine
+# "level = INFO" logs SQL queries.
+# "level = DEBUG" logs SQL queries and results.
+# "level = WARN" logs neither. (Recommended for production systems.)
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/authentication/production.ini b/docs/tutorials/wiki2/src/authentication/production.ini
new file mode 100644
index 000000000..a13a0ca19
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/production.ini
@@ -0,0 +1,62 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+
+auth.secret = real-seekrit
+
+[server:main]
+use = egg:waitress#main
+host = 0.0.0.0
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial, sqlalchemy
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_tutorial]
+level = WARN
+handlers =
+qualname = tutorial
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+# "level = INFO" logs SQL queries.
+# "level = DEBUG" logs SQL queries and results.
+# "level = WARN" logs neither. (Recommended for production systems.)
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/authentication/setup.py b/docs/tutorials/wiki2/src/authentication/setup.py
new file mode 100644
index 000000000..def3ce1f6
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/setup.py
@@ -0,0 +1,57 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'bcrypt',
+ 'docutils',
+ 'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'SQLAlchemy',
+ 'transaction',
+ 'zope.sqlalchemy',
+ 'waitress',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
+setup(name='tutorial',
+ version='0.0',
+ description='tutorial',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web wsgi bfg pylons pyramid',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ [console_scripts]
+ initialize_tutorial_db = tutorial.scripts.initializedb:main
+ """,
+ )
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/__init__.py b/docs/tutorials/wiki2/src/authentication/tutorial/__init__.py
new file mode 100644
index 000000000..f5c033b8b
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/__init__.py
@@ -0,0 +1,13 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ """ This function returns a Pyramid WSGI application.
+ """
+ config = Configurator(settings=settings)
+ config.include('pyramid_jinja2')
+ config.include('.models')
+ config.include('.routes')
+ config.include('.security')
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py
new file mode 100644
index 000000000..8147052ad
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/__init__.py
@@ -0,0 +1,74 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import configure_mappers
+import zope.sqlalchemy
+
+# import or define all models here to ensure they are attached to the
+# Base.metadata prior to any initialization routines
+from .page import Page # noqa
+from .user import User # noqa
+
+# run configure_mappers after defining all of the models to ensure
+# all relationships can be setup
+configure_mappers()
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_session_factory(engine):
+ factory = sessionmaker()
+ factory.configure(bind=engine)
+ return factory
+
+
+def get_tm_session(session_factory, transaction_manager):
+ """
+ Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
+
+ This function will hook the session to the transaction manager which
+ will take care of committing any changes.
+
+ - When using pyramid_tm it will automatically be committed or aborted
+ depending on whether an exception is raised.
+
+ - When using scripts you should wrap the session in a manager yourself.
+ For example::
+
+ import transaction
+
+ engine = get_engine(settings)
+ session_factory = get_session_factory(engine)
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ """
+ dbsession = session_factory()
+ zope.sqlalchemy.register(
+ dbsession, transaction_manager=transaction_manager)
+ return dbsession
+
+
+def includeme(config):
+ """
+ Initialize the model for a Pyramid app.
+
+ Activate this setup using ``config.include('tutorial.models')``.
+
+ """
+ settings = config.get_settings()
+
+ # use pyramid_tm to hook the transaction lifecycle to the request
+ config.include('pyramid_tm')
+
+ session_factory = get_session_factory(get_engine(settings))
+ config.registry['dbsession_factory'] = session_factory
+
+ # make request.dbsession available for use in Pyramid
+ config.add_request_method(
+ # r.tm is the transaction manager used by pyramid_tm
+ lambda r: get_tm_session(session_factory, r.tm),
+ 'dbsession',
+ reify=True
+ )
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py
new file mode 100644
index 000000000..0682247b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/meta.py
@@ -0,0 +1,16 @@
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.schema import MetaData
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/page.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/page.py
new file mode 100644
index 000000000..74ff1faf8
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/page.py
@@ -0,0 +1,20 @@
+from sqlalchemy import (
+ Column,
+ ForeignKey,
+ Integer,
+ Text,
+)
+from sqlalchemy.orm import relationship
+
+from .meta import Base
+
+
+class Page(Base):
+ """ The SQLAlchemy declarative model class for a Page object. """
+ __tablename__ = 'pages'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ data = Column(Text, nullable=False)
+
+ creator_id = Column(ForeignKey('users.id'), nullable=False)
+ creator = relationship('User', backref='created_pages')
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/models/user.py b/docs/tutorials/wiki2/src/authentication/tutorial/models/user.py
new file mode 100644
index 000000000..6fb32a1b2
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/models/user.py
@@ -0,0 +1,29 @@
+import bcrypt
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+)
+
+from .meta import Base
+
+
+class User(Base):
+ """ The SQLAlchemy declarative model class for a User object. """
+ __tablename__ = 'users'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ role = Column(Text, nullable=False)
+
+ password_hash = Column(Text)
+
+ def set_password(self, pw):
+ pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt())
+ self.password_hash = pwhash
+
+ def check_password(self, pw):
+ if self.password_hash is not None:
+ expected_hash = self.password_hash.encode('utf8')
+ actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash)
+ return expected_hash == actual_hash
+ return False
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/routes.py b/docs/tutorials/wiki2/src/authentication/tutorial/routes.py
new file mode 100644
index 000000000..cb747244f
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/routes.py
@@ -0,0 +1,8 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('view_wiki', '/')
+ config.add_route('login', '/login')
+ config.add_route('logout', '/logout')
+ config.add_route('view_page', '/{pagename}')
+ config.add_route('add_page', '/add_page/{pagename}')
+ config.add_route('edit_page', '/{pagename}/edit_page')
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/authentication/tutorial/scripts/__init__.py
new file mode 100644
index 000000000..5bb534f79
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/scripts/__init__.py
@@ -0,0 +1 @@
+# package
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/authentication/tutorial/scripts/initializedb.py
new file mode 100644
index 000000000..f3c0a6fef
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/scripts/initializedb.py
@@ -0,0 +1,57 @@
+import os
+import sys
+import transaction
+
+from pyramid.paster import (
+ get_appsettings,
+ setup_logging,
+ )
+
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import Base
+from ..models import (
+ get_engine,
+ get_session_factory,
+ get_tm_session,
+ )
+from ..models import Page, User
+
+
+def usage(argv):
+ cmd = os.path.basename(argv[0])
+ print('usage: %s <config_uri> [var=value]\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]
+ options = parse_vars(argv[2:])
+ setup_logging(config_uri)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
+ Base.metadata.create_all(engine)
+
+ session_factory = get_session_factory(engine)
+
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ editor = User(name='editor', role='editor')
+ editor.set_password('editor')
+ dbsession.add(editor)
+
+ basic = User(name='basic', role='basic')
+ basic.set_password('basic')
+ dbsession.add(basic)
+
+ page = Page(
+ name='FrontPage',
+ creator=editor,
+ data='This is the front page',
+ )
+ dbsession.add(page)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/security.py b/docs/tutorials/wiki2/src/authentication/tutorial/security.py
new file mode 100644
index 000000000..8ea3858d2
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/security.py
@@ -0,0 +1,27 @@
+from pyramid.authentication import AuthTktAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+
+from .models import User
+
+
+class MyAuthenticationPolicy(AuthTktAuthenticationPolicy):
+ def authenticated_userid(self, request):
+ user = request.user
+ if user is not None:
+ return user.id
+
+def get_user(request):
+ user_id = request.unauthenticated_userid
+ if user_id is not None:
+ user = request.dbsession.query(User).get(user_id)
+ return user
+
+def includeme(config):
+ settings = config.get_settings()
+ authn_policy = MyAuthenticationPolicy(
+ settings['auth.secret'],
+ hashalg='sha512',
+ )
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(ACLAuthorizationPolicy())
+ config.add_request_method(get_user, 'user', reify=True)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki2/src/authentication/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/static/pyramid.png b/docs/tutorials/wiki2/src/authentication/tutorial/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/static/theme.css b/docs/tutorials/wiki2/src/authentication/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/authentication/tutorial/templates/404.jinja2
new file mode 100644
index 000000000..37b0a16b6
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/templates/404.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid tutorial wiki</span> <span class="smaller">(based on TurboGears 20-Minute Wiki)</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/templates/edit.jinja2 b/docs/tutorials/wiki2/src/authentication/tutorial/templates/edit.jinja2
new file mode 100644
index 000000000..7db25c674
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/templates/edit.jinja2
@@ -0,0 +1,20 @@
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}Edit {{pagename}} - {% endblock subtitle %}
+
+{% block content %}
+<p>
+Editing <strong>{{pagename}}</strong>
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+<form action="{{ save_url }}" method="post">
+<div class="form-group">
+ <textarea class="form-control" name="body" rows="10" cols="60">{{ pagedata }}</textarea>
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2
index 0f564b16c..44d14304e 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/templates/layout.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,31 +22,27 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
- <div tal:replace="structure content">
- Page text goes here.
- </div>
- <p>
- <a tal:attributes="href edit_url" href="">
- Edit this page
- </a>
- </p>
- <p>
- Viewing <strong><span tal:replace="page.name">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
+ {% if request.user is none %}
+ <p class="pull-right">
+ <a href="{{ request.route_url('login') }}">Login</a>
+ </p>
+ {% else %}
+ <p class="pull-right">
+ {{request.user.name}} <a href="{{request.route_url('logout')}}">Logout</a>
+ </p>
+ {% endif %}
+ {% block content %}{% endblock %}
</div>
</div>
</div>
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/templates/login.jinja2 b/docs/tutorials/wiki2/src/authentication/tutorial/templates/login.jinja2
new file mode 100644
index 000000000..1806de0ff
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/templates/login.jinja2
@@ -0,0 +1,26 @@
+{% extends 'layout.jinja2' %}
+
+{% block title %}Login - {% endblock title %}
+
+{% block content %}
+<p>
+<strong>
+ Login
+</strong><br>
+{{ message }}
+</p>
+<form action="{{ url }}" method="post">
+<input type="hidden" name="next" value="{{ next_url }}">
+<div class="form-group">
+ <label for="login">Username</label>
+ <input type="text" name="login" value="{{ login }}">
+</div>
+<div class="form-group">
+ <label for="password">Password</label>
+ <input type="password" name="password">
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/templates/view.jinja2 b/docs/tutorials/wiki2/src/authentication/tutorial/templates/view.jinja2
new file mode 100644
index 000000000..94419e228
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/templates/view.jinja2
@@ -0,0 +1,18 @@
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}{{page.name}} - {% endblock subtitle %}
+
+{% block content %}
+<p>{{ content|safe }}</p>
+<p>
+<a href="{{ edit_url }}">
+ Edit this page
+</a>
+</p>
+<p>
+ Viewing <strong>{{page.name}}</strong>, created by <strong>{{page.creator.name}}</strong>.
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/tests.py b/docs/tutorials/wiki2/src/authentication/tutorial/tests.py
new file mode 100644
index 000000000..99e95efd3
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/tests.py
@@ -0,0 +1,65 @@
+import unittest
+import transaction
+
+from pyramid import testing
+
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+class BaseTest(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models')
+ settings = self.config.get_settings()
+
+ from .models import (
+ get_engine,
+ get_session_factory,
+ get_tm_session,
+ )
+
+ self.engine = get_engine(settings)
+ session_factory = get_session_factory(self.engine)
+
+ self.session = get_tm_session(session_factory, transaction.manager)
+
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
+
+ def tearDown(self):
+ from .models.meta import Base
+
+ testing.tearDown()
+ transaction.abort()
+ Base.metadata.drop_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
+
+ def setUp(self):
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
+
+ from .models import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info['one'].name, 'one')
+ self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
+
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py
new file mode 100644
index 000000000..2b993b430
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/auth.py
@@ -0,0 +1,46 @@
+from pyramid.httpexceptions import HTTPFound
+from pyramid.security import (
+ remember,
+ forget,
+ )
+from pyramid.view import (
+ forbidden_view_config,
+ view_config,
+)
+
+from ..models import User
+
+
+@view_config(route_name='login', renderer='../templates/login.jinja2')
+def login(request):
+ next_url = request.params.get('next', request.referrer)
+ if not next_url:
+ next_url = request.route_url('view_wiki')
+ message = ''
+ login = ''
+ if 'form.submitted' in request.params:
+ login = request.params['login']
+ password = request.params['password']
+ user = request.dbsession.query(User).filter_by(name=login).first()
+ if user is not None and user.check_password(password):
+ headers = remember(request, user.id)
+ return HTTPFound(location=next_url, headers=headers)
+ message = 'Failed login'
+
+ return dict(
+ message=message,
+ url=request.route_url('login'),
+ next_url=next_url,
+ login=login,
+ )
+
+@view_config(route_name='logout')
+def logout(request):
+ headers = forget(request)
+ next_url = request.route_url('view_wiki')
+ return HTTPFound(location=next_url, headers=headers)
+
+@forbidden_view_config()
+def forbidden_view(request):
+ next_url = request.route_url('login', _query={'next': request.url})
+ return HTTPFound(location=next_url)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
new file mode 100644
index 000000000..1b071434c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/default.py
@@ -0,0 +1,79 @@
+import cgi
+import re
+from docutils.core import publish_parts
+
+from pyramid.httpexceptions import (
+ HTTPForbidden,
+ HTTPFound,
+ HTTPNotFound,
+ )
+
+from pyramid.view import view_config
+
+from ..models import Page
+
+# regular expression used to find WikiWords
+wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
+
+@view_config(route_name='view_wiki')
+def view_wiki(request):
+ next_url = request.route_url('view_page', pagename='FrontPage')
+ return HTTPFound(location=next_url)
+
+@view_config(route_name='view_page', renderer='../templates/view.jinja2')
+def view_page(request):
+ pagename = request.matchdict['pagename']
+ page = request.dbsession.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ raise HTTPNotFound('No such page')
+
+ def add_link(match):
+ word = match.group(1)
+ exists = request.dbsession.query(Page).filter_by(name=word).all()
+ if exists:
+ view_url = request.route_url('view_page', pagename=word)
+ return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
+ else:
+ add_url = request.route_url('add_page', pagename=word)
+ return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
+
+ content = publish_parts(page.data, writer_name='html')['html_body']
+ content = wikiwords.sub(add_link, content)
+ edit_url = request.route_url('edit_page', pagename=page.name)
+ return dict(page=page, content=content, edit_url=edit_url)
+
+@view_config(route_name='edit_page', renderer='../templates/edit.jinja2')
+def edit_page(request):
+ pagename = request.matchdict['pagename']
+ page = request.dbsession.query(Page).filter_by(name=pagename).one()
+ user = request.user
+ if user is None or (user.role != 'editor' and page.creator != user):
+ raise HTTPForbidden
+ if 'form.submitted' in request.params:
+ page.data = request.params['body']
+ next_url = request.route_url('view_page', pagename=page.name)
+ return HTTPFound(location=next_url)
+ return dict(
+ pagename=page.name,
+ pagedata=page.data,
+ save_url=request.route_url('edit_page', pagename=page.name),
+ )
+
+@view_config(route_name='add_page', renderer='../templates/edit.jinja2')
+def add_page(request):
+ user = request.user
+ if user is None or user.role not in ('editor', 'basic'):
+ raise HTTPForbidden
+ pagename = request.matchdict['pagename']
+ if request.dbsession.query(Page).filter_by(name=pagename).count() > 0:
+ next_url = request.route_url('edit_page', pagename=pagename)
+ return HTTPFound(location=next_url)
+ if 'form.submitted' in request.params:
+ body = request.params['body']
+ page = Page(name=pagename, data=body)
+ page.creator = request.user
+ request.dbsession.add(page)
+ next_url = request.route_url('view_page', pagename=pagename)
+ return HTTPFound(location=next_url)
+ save_url = request.route_url('add_page', pagename=pagename)
+ return dict(pagename=pagename, pagedata='', save_url=save_url)
diff --git a/docs/tutorials/wiki2/src/authentication/tutorial/views/notfound.py b/docs/tutorials/wiki2/src/authentication/tutorial/views/notfound.py
new file mode 100644
index 000000000..69d6e2804
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authentication/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.jinja2')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki2/src/authorization/MANIFEST.in b/docs/tutorials/wiki2/src/authorization/MANIFEST.in
index 81beba1b1..42cd299b5 100644
--- a/docs/tutorials/wiki2/src/authorization/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/authorization/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt
index 68f430110..5b0101e5f 100644
--- a/docs/tutorials/wiki2/src/authorization/README.txt
+++ b/docs/tutorials/wiki2/src/authorization/README.txt
@@ -6,7 +6,7 @@ Getting Started
- cd <directory containing this file>
-- $VENV/bin/python setup.py develop
+- $VENV/bin/pip install -e .
- $VENV/bin/initialize_tutorial_db development.ini
diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini
index a9d53b296..4a6c9325c 100644
--- a/docs/tutorials/wiki2/src/authorization/development.ini
+++ b/docs/tutorials/wiki2/src/authorization/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -17,6 +17,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+auth.secret = seekrit
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -27,12 +29,12 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -68,4 +70,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini
index 4684d2f7a..a13a0ca19 100644
--- a/docs/tutorials/wiki2/src/authorization/production.ini
+++ b/docs/tutorials/wiki2/src/authorization/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -6,17 +11,20 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+auth.secret = real-seekrit
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -51,6 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py
index 09bd63d33..def3ce1f6 100644
--- a/docs/tutorials/wiki2/src/authorization/setup.py
+++ b/docs/tutorials/wiki2/src/authorization/setup.py
@@ -9,15 +9,22 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'bcrypt',
+ 'docutils',
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
'transaction',
'zope.sqlalchemy',
'waitress',
- 'docutils',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
]
setup(name='tutorial',
@@ -25,11 +32,11 @@ setup(name='tutorial',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -37,7 +44,9 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- test_suite='tutorial',
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
entry_points="""\
[paste.app_factory]
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
index 2ada42171..f5c033b8b 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -1,37 +1,13 @@
from pyramid.config import Configurator
-from pyramid.authentication import AuthTktAuthenticationPolicy
-from pyramid.authorization import ACLAuthorizationPolicy
-
-from sqlalchemy import engine_from_config
-
-from tutorial.security import groupfinder
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
- authn_policy = AuthTktAuthenticationPolicy(
- 'sosecret', callback=groupfinder, hashalg='sha512')
- authz_policy = ACLAuthorizationPolicy()
- config = Configurator(settings=settings,
- root_factory='tutorial.models.RootFactory')
- config.set_authentication_policy(authn_policy)
- config.set_authorization_policy(authz_policy)
- config.include('pyramid_chameleon')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.add_route('view_wiki', '/')
- config.add_route('login', '/login')
- config.add_route('logout', '/logout')
- config.add_route('view_page', '/{pagename}')
- config.add_route('add_page', '/add_page/{pagename}')
- config.add_route('edit_page', '/{pagename}/edit_page')
+ config = Configurator(settings=settings)
+ config.include('pyramid_jinja2')
+ config.include('.models')
+ config.include('.routes')
+ config.include('.security')
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py
deleted file mode 100644
index 4f7e1e024..000000000
--- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py
+++ /dev/null
@@ -1,37 +0,0 @@
-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):
- """ The SQLAlchemy declarative model class for a Page object. """
- __tablename__ = 'pages'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- data = Column(Text)
-
-
-class RootFactory(object):
- __acl__ = [ (Allow, Everyone, 'view'),
- (Allow, 'group:editors', 'edit') ]
- def __init__(self, request):
- pass
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
new file mode 100644
index 000000000..8147052ad
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
@@ -0,0 +1,74 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import configure_mappers
+import zope.sqlalchemy
+
+# import or define all models here to ensure they are attached to the
+# Base.metadata prior to any initialization routines
+from .page import Page # noqa
+from .user import User # noqa
+
+# run configure_mappers after defining all of the models to ensure
+# all relationships can be setup
+configure_mappers()
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_session_factory(engine):
+ factory = sessionmaker()
+ factory.configure(bind=engine)
+ return factory
+
+
+def get_tm_session(session_factory, transaction_manager):
+ """
+ Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
+
+ This function will hook the session to the transaction manager which
+ will take care of committing any changes.
+
+ - When using pyramid_tm it will automatically be committed or aborted
+ depending on whether an exception is raised.
+
+ - When using scripts you should wrap the session in a manager yourself.
+ For example::
+
+ import transaction
+
+ engine = get_engine(settings)
+ session_factory = get_session_factory(engine)
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ """
+ dbsession = session_factory()
+ zope.sqlalchemy.register(
+ dbsession, transaction_manager=transaction_manager)
+ return dbsession
+
+
+def includeme(config):
+ """
+ Initialize the model for a Pyramid app.
+
+ Activate this setup using ``config.include('tutorial.models')``.
+
+ """
+ settings = config.get_settings()
+
+ # use pyramid_tm to hook the transaction lifecycle to the request
+ config.include('pyramid_tm')
+
+ session_factory = get_session_factory(get_engine(settings))
+ config.registry['dbsession_factory'] = session_factory
+
+ # make request.dbsession available for use in Pyramid
+ config.add_request_method(
+ # r.tm is the transaction manager used by pyramid_tm
+ lambda r: get_tm_session(session_factory, r.tm),
+ 'dbsession',
+ reify=True
+ )
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py
new file mode 100644
index 000000000..0682247b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py
@@ -0,0 +1,16 @@
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.schema import MetaData
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/page.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/page.py
new file mode 100644
index 000000000..74ff1faf8
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/page.py
@@ -0,0 +1,20 @@
+from sqlalchemy import (
+ Column,
+ ForeignKey,
+ Integer,
+ Text,
+)
+from sqlalchemy.orm import relationship
+
+from .meta import Base
+
+
+class Page(Base):
+ """ The SQLAlchemy declarative model class for a Page object. """
+ __tablename__ = 'pages'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ data = Column(Text, nullable=False)
+
+ creator_id = Column(ForeignKey('users.id'), nullable=False)
+ creator = relationship('User', backref='created_pages')
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/user.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/user.py
new file mode 100644
index 000000000..6fb32a1b2
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/user.py
@@ -0,0 +1,29 @@
+import bcrypt
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+)
+
+from .meta import Base
+
+
+class User(Base):
+ """ The SQLAlchemy declarative model class for a User object. """
+ __tablename__ = 'users'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ role = Column(Text, nullable=False)
+
+ password_hash = Column(Text)
+
+ def set_password(self, pw):
+ pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt())
+ self.password_hash = pwhash
+
+ def check_password(self, pw):
+ if self.password_hash is not None:
+ expected_hash = self.password_hash.encode('utf8')
+ actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash)
+ return expected_hash == actual_hash
+ return False
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/routes.py b/docs/tutorials/wiki2/src/authorization/tutorial/routes.py
new file mode 100644
index 000000000..f0a8b7f96
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/routes.py
@@ -0,0 +1,56 @@
+from pyramid.httpexceptions import (
+ HTTPNotFound,
+ HTTPFound,
+)
+from pyramid.security import (
+ Allow,
+ Everyone,
+)
+
+from .models import Page
+
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('view_wiki', '/')
+ config.add_route('login', '/login')
+ config.add_route('logout', '/logout')
+ config.add_route('view_page', '/{pagename}', factory=page_factory)
+ config.add_route('add_page', '/add_page/{pagename}',
+ factory=new_page_factory)
+ config.add_route('edit_page', '/{pagename}/edit_page',
+ factory=page_factory)
+
+def new_page_factory(request):
+ pagename = request.matchdict['pagename']
+ if request.dbsession.query(Page).filter_by(name=pagename).count() > 0:
+ next_url = request.route_url('edit_page', pagename=pagename)
+ raise HTTPFound(location=next_url)
+ return NewPage(pagename)
+
+class NewPage(object):
+ def __init__(self, pagename):
+ self.pagename = pagename
+
+ def __acl__(self):
+ return [
+ (Allow, 'role:editor', 'create'),
+ (Allow, 'role:basic', 'create'),
+ ]
+
+def page_factory(request):
+ pagename = request.matchdict['pagename']
+ page = request.dbsession.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ raise HTTPNotFound
+ return PageResource(page)
+
+class PageResource(object):
+ def __init__(self, page):
+ self.page = page
+
+ def __acl__(self):
+ return [
+ (Allow, Everyone, 'view'),
+ (Allow, 'role:editor', 'edit'),
+ (Allow, str(self.page.creator_id), 'edit'),
+ ]
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py
index 23a5f13f4..f3c0a6fef 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py
@@ -2,36 +2,56 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import Base
from ..models import (
- DBSession,
- Page,
- Base,
+ get_engine,
+ get_session_factory,
+ get_tm_session,
)
+from ..models import Page, User
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
+ options = parse_vars(argv[2:])
setup_logging(config_uri)
- settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
Base.metadata.create_all(engine)
+
+ session_factory = get_session_factory(engine)
+
with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ editor = User(name='editor', role='editor')
+ editor.set_password('editor')
+ dbsession.add(editor)
+
+ basic = User(name='basic', role='basic')
+ basic.set_password('basic')
+ dbsession.add(basic)
+
+ page = Page(
+ name='FrontPage',
+ creator=editor,
+ data='This is the front page',
+ )
+ dbsession.add(page)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/security.py b/docs/tutorials/wiki2/src/authorization/tutorial/security.py
index d88c9c71f..25cff7b05 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/security.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/security.py
@@ -1,7 +1,40 @@
-USERS = {'editor':'editor',
- 'viewer':'viewer'}
-GROUPS = {'editor':['group:editors']}
+from pyramid.authentication import AuthTktAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+from pyramid.security import (
+ Authenticated,
+ Everyone,
+)
-def groupfinder(userid, request):
- if userid in USERS:
- return GROUPS.get(userid, [])
+from .models import User
+
+
+class MyAuthenticationPolicy(AuthTktAuthenticationPolicy):
+ def authenticated_userid(self, request):
+ user = request.user
+ if user is not None:
+ return user.id
+
+ def effective_principals(self, request):
+ principals = [Everyone]
+ user = request.user
+ if user is not None:
+ principals.append(Authenticated)
+ principals.append(str(user.id))
+ principals.append('role:' + user.role)
+ return principals
+
+def get_user(request):
+ user_id = request.unauthenticated_userid
+ if user_id is not None:
+ user = request.dbsession.query(User).get(user_id)
+ return user
+
+def includeme(config):
+ settings = config.get_settings()
+ authn_policy = MyAuthenticationPolicy(
+ settings['auth.secret'],
+ hashalg='sha512',
+ )
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(ACLAuthorizationPolicy())
+ config.add_request_method(get_user, 'user', reify=True)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/authorization/tutorial/templates/404.jinja2
new file mode 100644
index 000000000..37b0a16b6
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/404.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid tutorial wiki</span> <span class="smaller">(based on TurboGears 20-Minute Wiki)</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.jinja2 b/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.jinja2
new file mode 100644
index 000000000..7db25c674
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.jinja2
@@ -0,0 +1,20 @@
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}Edit {{pagename}} - {% endblock subtitle %}
+
+{% block content %}
+<p>
+Editing <strong>{{pagename}}</strong>
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+<form action="{{ save_url }}" method="post">
+<div class="form-group">
+ <textarea class="form-control" name="body" rows="10" cols="60">{{ pagedata }}</textarea>
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt
deleted file mode 100644
index ed355434d..000000000
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt
+++ /dev/null
@@ -1,72 +0,0 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
-
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
- <![endif]-->
- </head>
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p tal:condition="logged_in" class="pull-right">
- <a href="${request.application_url}/logout">Logout</a>
- </p>
- <p>
- Editing <strong><span tal:replace="page.name">Page Name Goes
- Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <form action="${save_url}" method="post">
- <div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
- </div>
- <div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
- </div>
- </form>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
- </body>
-</html>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
index 02cb8e73b..44d14304e 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,34 +22,27 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
- <p tal:condition="logged_in" class="pull-right">
- <a href="${request.application_url}/logout">Logout</a>
- </p>
- <div tal:replace="structure content">
- Page text goes here.
- </div>
- <p>
- <a tal:attributes="href edit_url" href="">
- Edit this page
- </a>
- </p>
- <p>
- Viewing <strong><span tal:replace="page.name">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
+ {% if request.user is none %}
+ <p class="pull-right">
+ <a href="{{ request.route_url('login') }}">Login</a>
+ </p>
+ {% else %}
+ <p class="pull-right">
+ {{request.user.name}} <a href="{{request.route_url('logout')}}">Logout</a>
+ </p>
+ {% endif %}
+ {% block content %}{% endblock %}
</div>
</div>
</div>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.jinja2 b/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.jinja2
new file mode 100644
index 000000000..1806de0ff
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.jinja2
@@ -0,0 +1,26 @@
+{% extends 'layout.jinja2' %}
+
+{% block title %}Login - {% endblock title %}
+
+{% block content %}
+<p>
+<strong>
+ Login
+</strong><br>
+{{ message }}
+</p>
+<form action="{{ url }}" method="post">
+<input type="hidden" name="next" value="{{ next_url }}">
+<div class="form-group">
+ <label for="login">Username</label>
+ <input type="text" name="login" value="{{ login }}">
+</div>
+<div class="form-group">
+ <label for="password">Password</label>
+ <input type="password" name="password">
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt
deleted file mode 100644
index 4a938e9bb..000000000
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt
+++ /dev/null
@@ -1,74 +0,0 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
-
- <title>Login - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
- <![endif]-->
- </head>
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p>
- <strong>
- Login
- </strong><br>
- <span tal:replace="message"></span>
- </p>
- <form action="${url}" method="post">
- <input type="hidden" name="came_from" value="${came_from}">
- <div class="form-group">
- <label for="login">Username</label>
- <input type="text" name="login" value="${login}">
- </div>
- <div class="form-group">
- <label for="password">Password</label>
- <input type="password" name="password" value="${password}">
- </div>
- <div class="form-group">
- <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
- </div>
- </form>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
- </body>
-</html>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.jinja2 b/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.jinja2
new file mode 100644
index 000000000..94419e228
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.jinja2
@@ -0,0 +1,18 @@
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}{{page.name}} - {% endblock subtitle %}
+
+{% block content %}
+<p>{{ content|safe }}</p>
+<p>
+<a href="{{ edit_url }}">
+ Edit this page
+</a>
+</p>
+<p>
+ Viewing <strong>{{page.name}}</strong>, created by <strong>{{page.creator.name}}</strong>.
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py
index 9f01d2da5..99e95efd3 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py
@@ -3,144 +3,63 @@ import transaction
from pyramid import testing
-def _initTestingDB():
- from sqlalchemy import create_engine
- from tutorial.models import (
- DBSession,
- Page,
- Base
- )
- engine = create_engine('sqlite://')
- Base.metadata.create_all(engine)
- DBSession.configure(bind=engine)
- with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
- return DBSession
-
-def _registerRoutes(config):
- config.add_route('view_page', '{pagename}')
- config.add_route('edit_page', '{pagename}/edit_page')
- config.add_route('add_page', 'add_page/{pagename}')
-
-class ViewWikiTests(unittest.TestCase):
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- self.session = _initTestingDB()
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models')
+ settings = self.config.get_settings()
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
+ from .models import (
+ get_engine,
+ get_session_factory,
+ get_tm_session,
+ )
- def _callFUT(self, request):
- from tutorial.views import view_wiki
- return view_wiki(request)
+ self.engine = get_engine(settings)
+ session_factory = get_session_factory(self.engine)
- def test_it(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
+ self.session = get_tm_session(session_factory, transaction.manager)
-class ViewPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import view_page
- return view_page(request)
-
- def test_it(self):
- from tutorial.models import Page
- request = testing.DummyRequest()
- request.matchdict['pagename'] = 'IDoExist'
- page = Page(name='IDoExist', data='Hello CruelWorld IDoExist')
- self.session.add(page)
- _registerRoutes(self.config)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(
- info['content'],
- '<div class="document">\n'
- '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
- 'CruelWorld</a> '
- '<a href="http://example.com/IDoExist">'
- 'IDoExist</a>'
- '</p>\n</div>\n')
- self.assertEqual(info['edit_url'],
- 'http://example.com/IDoExist/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ from .models.meta import Base
- def tearDown(self):
- self.session.remove()
testing.tearDown()
+ transaction.abort()
+ Base.metadata.drop_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def _callFUT(self, request):
- from tutorial.views import add_page
- return add_page(request)
-
- def test_it_notsubmitted(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'AnotherPage'}
- info = self._callFUT(request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(info['save_url'],
- 'http://example.com/add_page/AnotherPage')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'AnotherPage'}
- self._callFUT(request)
- page = self.session.query(Page).filter_by(name='AnotherPage').one()
- self.assertEqual(page.data, 'Hello yo!')
-
-class EditPageTests(unittest.TestCase):
def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
+ from .models import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info['one'].name, 'one')
+ self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
- def _callFUT(self, request):
- from tutorial.views import edit_page
- return edit_page(request)
-
- def test_it_notsubmitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(info['save_url'],
- 'http://example.com/abc/edit_page')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/abc')
- self.assertEqual(page.data, 'Hello yo!')
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
deleted file mode 100644
index e954d5a31..000000000
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import re
-from docutils.core import publish_parts
-
-from pyramid.httpexceptions import (
- HTTPFound,
- HTTPNotFound,
- )
-
-from pyramid.view import (
- view_config,
- forbidden_view_config,
- )
-
-from pyramid.security import (
- remember,
- forget,
- )
-
-from .security import USERS
-
-from .models import (
- DBSession,
- Page,
- )
-
-
-# regular expression used to find WikiWords
-wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-
-@view_config(route_name='view_wiki',
- permission='view')
-def view_wiki(request):
- return HTTPFound(location = request.route_url('view_page',
- pagename='FrontPage'))
-
-@view_config(route_name='view_page', renderer='templates/view.pt',
- permission='view')
-def view_page(request):
- pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).first()
- if page is None:
- return HTTPNotFound('No such page')
-
- def check(match):
- word = match.group(1)
- exists = DBSession.query(Page).filter_by(name=word).all()
- if exists:
- view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, word)
- else:
- add_url = request.route_url('add_page', pagename=word)
- return '<a href="%s">%s</a>' % (add_url, word)
-
- content = publish_parts(page.data, writer_name='html')['html_body']
- content = wikiwords.sub(check, content)
- edit_url = request.route_url('edit_page', pagename=pagename)
- return dict(page=page, content=content, edit_url=edit_url,
- logged_in=request.authenticated_userid)
-
-@view_config(route_name='add_page', renderer='templates/edit.pt',
- permission='edit')
-def add_page(request):
- pagename = request.matchdict['pagename']
- if 'form.submitted' in request.params:
- body = request.params['body']
- page = Page(name=pagename, data=body)
- DBSession.add(page)
- return HTTPFound(location = request.route_url('view_page',
- pagename=pagename))
- save_url = request.route_url('add_page', pagename=pagename)
- page = Page(name='', data='')
- return dict(page=page, save_url=save_url,
- logged_in=request.authenticated_userid)
-
-@view_config(route_name='edit_page', renderer='templates/edit.pt',
- permission='edit')
-def edit_page(request):
- pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).one()
- if 'form.submitted' in request.params:
- page.data = request.params['body']
- DBSession.add(page)
- return HTTPFound(location = request.route_url('view_page',
- pagename=pagename))
- return dict(
- page=page,
- save_url=request.route_url('edit_page', pagename=pagename),
- logged_in=request.authenticated_userid
- )
-
-@view_config(route_name='login', renderer='templates/login.pt')
-@forbidden_view_config(renderer='templates/login.pt')
-def login(request):
- login_url = request.route_url('login')
- referrer = request.url
- if referrer == login_url:
- referrer = '/' # never use the login form itself as came_from
- came_from = request.params.get('came_from', referrer)
- message = ''
- login = ''
- password = ''
- if 'form.submitted' in request.params:
- login = request.params['login']
- password = request.params['password']
- if USERS.get(login) == password:
- headers = remember(request, login)
- return HTTPFound(location = came_from,
- headers = headers)
- message = 'Failed login'
-
- return dict(
- message = message,
- url = request.application_url + '/login',
- came_from = came_from,
- login = login,
- password = password,
- )
-
-@view_config(route_name='logout')
-def logout(request):
- headers = forget(request)
- return HTTPFound(location = request.route_url('view_wiki'),
- headers = headers)
-
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py
new file mode 100644
index 000000000..2b993b430
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/auth.py
@@ -0,0 +1,46 @@
+from pyramid.httpexceptions import HTTPFound
+from pyramid.security import (
+ remember,
+ forget,
+ )
+from pyramid.view import (
+ forbidden_view_config,
+ view_config,
+)
+
+from ..models import User
+
+
+@view_config(route_name='login', renderer='../templates/login.jinja2')
+def login(request):
+ next_url = request.params.get('next', request.referrer)
+ if not next_url:
+ next_url = request.route_url('view_wiki')
+ message = ''
+ login = ''
+ if 'form.submitted' in request.params:
+ login = request.params['login']
+ password = request.params['password']
+ user = request.dbsession.query(User).filter_by(name=login).first()
+ if user is not None and user.check_password(password):
+ headers = remember(request, user.id)
+ return HTTPFound(location=next_url, headers=headers)
+ message = 'Failed login'
+
+ return dict(
+ message=message,
+ url=request.route_url('login'),
+ next_url=next_url,
+ login=login,
+ )
+
+@view_config(route_name='logout')
+def logout(request):
+ headers = forget(request)
+ next_url = request.route_url('view_wiki')
+ return HTTPFound(location=next_url, headers=headers)
+
+@forbidden_view_config()
+def forbidden_view(request):
+ next_url = request.route_url('login', _query={'next': request.url})
+ return HTTPFound(location=next_url)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
new file mode 100644
index 000000000..9358993ea
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
@@ -0,0 +1,64 @@
+import cgi
+import re
+from docutils.core import publish_parts
+
+from pyramid.httpexceptions import HTTPFound
+from pyramid.view import view_config
+
+from ..models import Page
+
+# regular expression used to find WikiWords
+wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
+
+@view_config(route_name='view_wiki')
+def view_wiki(request):
+ next_url = request.route_url('view_page', pagename='FrontPage')
+ return HTTPFound(location=next_url)
+
+@view_config(route_name='view_page', renderer='../templates/view.jinja2',
+ permission='view')
+def view_page(request):
+ page = request.context.page
+
+ def add_link(match):
+ word = match.group(1)
+ exists = request.dbsession.query(Page).filter_by(name=word).all()
+ if exists:
+ view_url = request.route_url('view_page', pagename=word)
+ return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
+ else:
+ add_url = request.route_url('add_page', pagename=word)
+ return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
+
+ content = publish_parts(page.data, writer_name='html')['html_body']
+ content = wikiwords.sub(add_link, content)
+ edit_url = request.route_url('edit_page', pagename=page.name)
+ return dict(page=page, content=content, edit_url=edit_url)
+
+@view_config(route_name='edit_page', renderer='../templates/edit.jinja2',
+ permission='edit')
+def edit_page(request):
+ page = request.context.page
+ if 'form.submitted' in request.params:
+ page.data = request.params['body']
+ next_url = request.route_url('view_page', pagename=page.name)
+ return HTTPFound(location=next_url)
+ return dict(
+ pagename=page.name,
+ pagedata=page.data,
+ save_url=request.route_url('edit_page', pagename=page.name),
+ )
+
+@view_config(route_name='add_page', renderer='../templates/edit.jinja2',
+ permission='create')
+def add_page(request):
+ pagename = request.context.pagename
+ if 'form.submitted' in request.params:
+ body = request.params['body']
+ page = Page(name=pagename, data=body)
+ page.creator = request.user
+ request.dbsession.add(page)
+ next_url = request.route_url('view_page', pagename=pagename)
+ return HTTPFound(location=next_url)
+ save_url = request.route_url('add_page', pagename=pagename)
+ return dict(pagename=pagename, pagedata='', save_url=save_url)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/notfound.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/notfound.py
new file mode 100644
index 000000000..69d6e2804
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.jinja2')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in
index 81beba1b1..42cd299b5 100644
--- a/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt
index 68f430110..5b0101e5f 100644
--- a/docs/tutorials/wiki2/src/basiclayout/README.txt
+++ b/docs/tutorials/wiki2/src/basiclayout/README.txt
@@ -6,7 +6,7 @@ Getting Started
- cd <directory containing this file>
-- $VENV/bin/python setup.py develop
+- $VENV/bin/pip install -e .
- $VENV/bin/initialize_tutorial_db development.ini
diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini
index a9d53b296..22b733e10 100644
--- a/docs/tutorials/wiki2/src/basiclayout/development.ini
+++ b/docs/tutorials/wiki2/src/basiclayout/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -27,12 +27,12 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini
index fa94c1b3e..d2ecfe22a 100644
--- a/docs/tutorials/wiki2/src/basiclayout/production.ini
+++ b/docs/tutorials/wiki2/src/basiclayout/production.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -11,8 +11,6 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -23,7 +21,7 @@ port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -59,4 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py
index 15e7e5923..ede0a82ef 100644
--- a/docs/tutorials/wiki2/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -10,7 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
@@ -19,16 +19,22 @@ requires = [
'waitress',
]
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='tutorial',
version='0.0',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -36,7 +42,9 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- test_suite='tutorial',
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
entry_points="""\
[paste.app_factory]
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
index 867049e4f..4dab44823 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
@@ -1,21 +1,12 @@
from pyramid.config import Configurator
-from sqlalchemy import engine_from_config
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.add_route('home', '/')
+ config.include('pyramid_jinja2')
+ config.include('.models')
+ config.include('.routes')
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py
deleted file mode 100644
index 11ddccadb..000000000
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from sqlalchemy import (
- Column,
- Integer,
- Text,
- Index,
- )
-
-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 MyModel(Base):
- __tablename__ = 'models'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- value = Column(Integer)
-
-Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
new file mode 100644
index 000000000..3fc82cfba
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
@@ -0,0 +1,73 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import configure_mappers
+import zope.sqlalchemy
+
+# import or define all models here to ensure they are attached to the
+# Base.metadata prior to any initialization routines
+from .mymodel import MyModel # noqa
+
+# run configure_mappers after defining all of the models to ensure
+# all relationships can be setup
+configure_mappers()
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_session_factory(engine):
+ factory = sessionmaker()
+ factory.configure(bind=engine)
+ return factory
+
+
+def get_tm_session(session_factory, transaction_manager):
+ """
+ Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
+
+ This function will hook the session to the transaction manager which
+ will take care of committing any changes.
+
+ - When using pyramid_tm it will automatically be committed or aborted
+ depending on whether an exception is raised.
+
+ - When using scripts you should wrap the session in a manager yourself.
+ For example::
+
+ import transaction
+
+ engine = get_engine(settings)
+ session_factory = get_session_factory(engine)
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ """
+ dbsession = session_factory()
+ zope.sqlalchemy.register(
+ dbsession, transaction_manager=transaction_manager)
+ return dbsession
+
+
+def includeme(config):
+ """
+ Initialize the model for a Pyramid app.
+
+ Activate this setup using ``config.include('tutorial.models')``.
+
+ """
+ settings = config.get_settings()
+
+ # use pyramid_tm to hook the transaction lifecycle to the request
+ config.include('pyramid_tm')
+
+ session_factory = get_session_factory(get_engine(settings))
+ config.registry['dbsession_factory'] = session_factory
+
+ # make request.dbsession available for use in Pyramid
+ config.add_request_method(
+ # r.tm is the transaction manager used by pyramid_tm
+ lambda r: get_tm_session(session_factory, r.tm),
+ 'dbsession',
+ reify=True
+ )
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py
new file mode 100644
index 000000000..0682247b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py
@@ -0,0 +1,16 @@
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.schema import MetaData
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py
new file mode 100644
index 000000000..d65a01a42
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py
@@ -0,0 +1,18 @@
+from sqlalchemy import (
+ Column,
+ Index,
+ Integer,
+ Text,
+)
+
+from .meta import Base
+
+
+class MyModel(Base):
+ __tablename__ = 'models'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text)
+ value = Column(Integer)
+
+
+Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/routes.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/routes.py
new file mode 100644
index 000000000..25504ad4d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/routes.py
@@ -0,0 +1,3 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('home', '/')
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py
index 66feb3008..7307ecc5c 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py
@@ -2,36 +2,44 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import Base
from ..models import (
- DBSession,
- MyModel,
- Base,
+ get_engine,
+ get_session_factory,
+ get_tm_session,
)
+from ..models import MyModel
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
+ options = parse_vars(argv[2:])
setup_logging(config_uri)
- settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
Base.metadata.create_all(engine)
+
+ session_factory = get_session_factory(engine)
+
with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
model = MyModel(name='one', value=1)
- DBSession.add(model)
+ dbsession.add(model)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/404.jinja2
new file mode 100644
index 000000000..1917f83c7
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/404.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
index c9b0cec21..ab8c5ea3d 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
@@ -1,12 +1,12 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
<title>Alchemy Scaffold for The Pyramid Web Framework</title>
@@ -14,7 +14,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
new file mode 100644
index 000000000..6b49869c4
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
index 57a775e0a..99e95efd3 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
@@ -3,31 +3,63 @@ import transaction
from pyramid import testing
-from .models import DBSession
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
-class TestMyView(unittest.TestCase):
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- from sqlalchemy import create_engine
- engine = create_engine('sqlite://')
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models')
+ settings = self.config.get_settings()
+
from .models import (
- Base,
- MyModel,
+ get_engine,
+ get_session_factory,
+ get_tm_session,
)
- DBSession.configure(bind=engine)
- Base.metadata.create_all(engine)
- with transaction.manager:
- model = MyModel(name='one', value=55)
- DBSession.add(model)
+
+ self.engine = get_engine(settings)
+ session_factory = get_session_factory(self.engine)
+
+ self.session = get_tm_session(session_factory, transaction.manager)
+
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- DBSession.remove()
+ from .models.meta import Base
+
testing.tearDown()
+ transaction.abort()
+ Base.metadata.drop_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def test_it(self):
- from .views import my_view
- request = testing.DummyRequest()
- info = my_view(request)
+ def setUp(self):
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
+
+ from .models import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
+
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/models/tutorial/views.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py
index 4cfcae4af..ad0c728d7 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py
@@ -3,26 +3,25 @@ from pyramid.view import view_config
from sqlalchemy.exc import DBAPIError
-from .models import (
- DBSession,
- MyModel,
- )
+from ..models import MyModel
-@view_config(route_name='home', renderer='templates/mytemplate.pt')
+@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
def my_view(request):
try:
- one = DBSession.query(MyModel).filter(MyModel.name == 'one').first()
+ query = request.dbsession.query(MyModel)
+ one = query.filter(MyModel.name == 'one').first()
except DBAPIError:
- return Response(conn_err_msg, content_type='text/plain', status_int=500)
+ return Response(db_err_msg, content_type='text/plain', status=500)
return {'one': one, 'project': 'tutorial'}
-conn_err_msg = """\
+
+db_err_msg = """\
Pyramid is having a problem using your SQL database. The problem
might be caused by one of the following things:
1. You may need to run the "initialize_tutorial_db" script
- to initialize your database tables. Check your virtual
+ to initialize your database tables. Check your virtual
environment's "bin" directory for this script and try to run it.
2. Your database server may not be running. Check that the
@@ -32,4 +31,3 @@ might be caused by one of the following things:
After you fix the problem, please restart the Pyramid application to
try it again.
"""
-
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views/notfound.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/notfound.py
new file mode 100644
index 000000000..69d6e2804
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.jinja2')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki2/src/installation/CHANGES.txt b/docs/tutorials/wiki2/src/installation/CHANGES.txt
new file mode 100644
index 000000000..35a34f332
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/CHANGES.txt
@@ -0,0 +1,4 @@
+0.0
+---
+
+- Initial version
diff --git a/docs/tutorials/wiki2/src/installation/MANIFEST.in b/docs/tutorials/wiki2/src/installation/MANIFEST.in
new file mode 100644
index 000000000..42cd299b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.txt *.ini *.cfg *.rst
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki2/src/installation/README.txt b/docs/tutorials/wiki2/src/installation/README.txt
new file mode 100644
index 000000000..5b0101e5f
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/README.txt
@@ -0,0 +1,14 @@
+tutorial README
+==================
+
+Getting Started
+---------------
+
+- cd <directory containing this file>
+
+- $VENV/bin/pip install -e .
+
+- $VENV/bin/initialize_tutorial_db development.ini
+
+- $VENV/bin/pserve development.ini
+
diff --git a/docs/tutorials/wiki2/src/installation/development.ini b/docs/tutorials/wiki2/src/installation/development.ini
new file mode 100644
index 000000000..22b733e10
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/development.ini
@@ -0,0 +1,71 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = true
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+pyramid.includes =
+ pyramid_debugtoolbar
+ pyramid_tm
+
+sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+
+# By default, the toolbar only appears for clients from IP addresses
+# '127.0.0.1' and '::1'.
+# debugtoolbar.hosts = 127.0.0.1 ::1
+
+###
+# wsgi server configuration
+###
+
+[server:main]
+use = egg:waitress#main
+host = 127.0.0.1
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial, sqlalchemy
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = INFO
+handlers = console
+
+[logger_tutorial]
+level = DEBUG
+handlers =
+qualname = tutorial
+
+[logger_sqlalchemy]
+level = INFO
+handlers =
+qualname = sqlalchemy.engine
+# "level = INFO" logs SQL queries.
+# "level = DEBUG" logs SQL queries and results.
+# "level = WARN" logs neither. (Recommended for production systems.)
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/installation/production.ini b/docs/tutorials/wiki2/src/installation/production.ini
new file mode 100644
index 000000000..d2ecfe22a
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/production.ini
@@ -0,0 +1,60 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
+[app:main]
+use = egg:tutorial
+
+pyramid.reload_templates = false
+pyramid.debug_authorization = false
+pyramid.debug_notfound = false
+pyramid.debug_routematch = false
+pyramid.default_locale_name = en
+
+sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+
+[server:main]
+use = egg:waitress#main
+host = 0.0.0.0
+port = 6543
+
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
+
+[loggers]
+keys = root, tutorial, sqlalchemy
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+
+[logger_tutorial]
+level = WARN
+handlers =
+qualname = tutorial
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+# "level = INFO" logs SQL queries.
+# "level = DEBUG" logs SQL queries and results.
+# "level = WARN" logs neither. (Recommended for production systems.)
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/installation/setup.py b/docs/tutorials/wiki2/src/installation/setup.py
new file mode 100644
index 000000000..ede0a82ef
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/setup.py
@@ -0,0 +1,55 @@
+import os
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+with open(os.path.join(here, 'README.txt')) as f:
+ README = f.read()
+with open(os.path.join(here, 'CHANGES.txt')) as f:
+ CHANGES = f.read()
+
+requires = [
+ 'pyramid',
+ 'pyramid_jinja2',
+ 'pyramid_debugtoolbar',
+ 'pyramid_tm',
+ 'SQLAlchemy',
+ 'transaction',
+ 'zope.sqlalchemy',
+ 'waitress',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
+setup(name='tutorial',
+ version='0.0',
+ description='tutorial',
+ long_description=README + '\n\n' + CHANGES,
+ classifiers=[
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
+ author='',
+ author_email='',
+ url='',
+ keywords='web wsgi bfg pylons pyramid',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ extras_require={
+ 'testing': tests_require,
+ },
+ install_requires=requires,
+ entry_points="""\
+ [paste.app_factory]
+ main = tutorial:main
+ [console_scripts]
+ initialize_tutorial_db = tutorial.scripts.initializedb:main
+ """,
+ )
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/__init__.py
new file mode 100644
index 000000000..4dab44823
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/__init__.py
@@ -0,0 +1,12 @@
+from pyramid.config import Configurator
+
+
+def main(global_config, **settings):
+ """ This function returns a Pyramid WSGI application.
+ """
+ config = Configurator(settings=settings)
+ config.include('pyramid_jinja2')
+ config.include('.models')
+ config.include('.routes')
+ config.scan()
+ return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
new file mode 100644
index 000000000..3fc82cfba
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/models/__init__.py
@@ -0,0 +1,73 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import configure_mappers
+import zope.sqlalchemy
+
+# import or define all models here to ensure they are attached to the
+# Base.metadata prior to any initialization routines
+from .mymodel import MyModel # noqa
+
+# run configure_mappers after defining all of the models to ensure
+# all relationships can be setup
+configure_mappers()
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_session_factory(engine):
+ factory = sessionmaker()
+ factory.configure(bind=engine)
+ return factory
+
+
+def get_tm_session(session_factory, transaction_manager):
+ """
+ Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
+
+ This function will hook the session to the transaction manager which
+ will take care of committing any changes.
+
+ - When using pyramid_tm it will automatically be committed or aborted
+ depending on whether an exception is raised.
+
+ - When using scripts you should wrap the session in a manager yourself.
+ For example::
+
+ import transaction
+
+ engine = get_engine(settings)
+ session_factory = get_session_factory(engine)
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ """
+ dbsession = session_factory()
+ zope.sqlalchemy.register(
+ dbsession, transaction_manager=transaction_manager)
+ return dbsession
+
+
+def includeme(config):
+ """
+ Initialize the model for a Pyramid app.
+
+ Activate this setup using ``config.include('tutorial.models')``.
+
+ """
+ settings = config.get_settings()
+
+ # use pyramid_tm to hook the transaction lifecycle to the request
+ config.include('pyramid_tm')
+
+ session_factory = get_session_factory(get_engine(settings))
+ config.registry['dbsession_factory'] = session_factory
+
+ # make request.dbsession available for use in Pyramid
+ config.add_request_method(
+ # r.tm is the transaction manager used by pyramid_tm
+ lambda r: get_tm_session(session_factory, r.tm),
+ 'dbsession',
+ reify=True
+ )
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py b/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py
new file mode 100644
index 000000000..0682247b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/models/meta.py
@@ -0,0 +1,16 @@
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.schema import MetaData
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/installation/tutorial/models/mymodel.py
new file mode 100644
index 000000000..d65a01a42
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/models/mymodel.py
@@ -0,0 +1,18 @@
+from sqlalchemy import (
+ Column,
+ Index,
+ Integer,
+ Text,
+)
+
+from .meta import Base
+
+
+class MyModel(Base):
+ __tablename__ = 'models'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text)
+ value = Column(Integer)
+
+
+Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/routes.py b/docs/tutorials/wiki2/src/installation/tutorial/routes.py
new file mode 100644
index 000000000..25504ad4d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/routes.py
@@ -0,0 +1,3 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('home', '/')
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/scripts/__init__.py
new file mode 100644
index 000000000..5bb534f79
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/scripts/__init__.py
@@ -0,0 +1 @@
+# package
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/installation/tutorial/scripts/initializedb.py
new file mode 100644
index 000000000..7307ecc5c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/scripts/initializedb.py
@@ -0,0 +1,45 @@
+import os
+import sys
+import transaction
+
+from pyramid.paster import (
+ get_appsettings,
+ setup_logging,
+ )
+
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import Base
+from ..models import (
+ get_engine,
+ get_session_factory,
+ get_tm_session,
+ )
+from ..models import MyModel
+
+
+def usage(argv):
+ cmd = os.path.basename(argv[0])
+ print('usage: %s <config_uri> [var=value]\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]
+ options = parse_vars(argv[2:])
+ setup_logging(config_uri)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
+ Base.metadata.create_all(engine)
+
+ session_factory = get_session_factory(engine)
+
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ model = MyModel(name='one', value=1)
+ dbsession.add(model)
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/static/pyramid-16x16.png b/docs/tutorials/wiki2/src/installation/tutorial/static/pyramid-16x16.png
new file mode 100644
index 000000000..979203112
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/static/pyramid-16x16.png
Binary files differ
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/static/pyramid.png b/docs/tutorials/wiki2/src/installation/tutorial/static/pyramid.png
new file mode 100644
index 000000000..4ab837be9
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/static/pyramid.png
Binary files differ
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/static/theme.css b/docs/tutorials/wiki2/src/installation/tutorial/static/theme.css
new file mode 100644
index 000000000..0f4b1a4d4
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/static/theme.css
@@ -0,0 +1,154 @@
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
+body {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+ color: #ffffff;
+ background: #bc2131;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 300;
+}
+p {
+ font-weight: 300;
+}
+.font-normal {
+ font-weight: 400;
+}
+.font-semi-bold {
+ font-weight: 600;
+}
+.font-bold {
+ font-weight: 700;
+}
+.starter-template {
+ margin-top: 250px;
+}
+.starter-template .content {
+ margin-left: 10px;
+}
+.starter-template .content h1 {
+ margin-top: 10px;
+ font-size: 60px;
+}
+.starter-template .content h1 .smaller {
+ font-size: 40px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead {
+ font-size: 25px;
+ color: #f2b7bd;
+}
+.starter-template .content .lead .font-normal {
+ color: #ffffff;
+}
+.starter-template .links {
+ float: right;
+ right: 0;
+ margin-top: 125px;
+}
+.starter-template .links ul {
+ display: block;
+ padding: 0;
+ margin: 0;
+}
+.starter-template .links ul li {
+ list-style: none;
+ display: inline;
+ margin: 0 10px;
+}
+.starter-template .links ul li:first-child {
+ margin-left: 0;
+}
+.starter-template .links ul li:last-child {
+ margin-right: 0;
+}
+.starter-template .links ul li.current-version {
+ color: #f2b7bd;
+ font-weight: 400;
+}
+.starter-template .links ul li a, a {
+ color: #f2b7bd;
+ text-decoration: underline;
+}
+.starter-template .links ul li a:hover, a:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+.starter-template .links ul li .icon-muted {
+ color: #eb8b95;
+ margin-right: 5px;
+}
+.starter-template .links ul li:hover .icon-muted {
+ color: #ffffff;
+}
+.starter-template .copyright {
+ margin-top: 10px;
+ font-size: 0.9em;
+ color: #f2b7bd;
+ text-transform: lowercase;
+ float: right;
+ right: 0;
+}
+@media (max-width: 1199px) {
+ .starter-template .content h1 {
+ font-size: 45px;
+ }
+ .starter-template .content h1 .smaller {
+ font-size: 30px;
+ }
+ .starter-template .content .lead {
+ font-size: 20px;
+ }
+}
+@media (max-width: 991px) {
+ .starter-template {
+ margin-top: 0;
+ }
+ .starter-template .logo {
+ margin: 40px auto;
+ }
+ .starter-template .content {
+ margin-left: 0;
+ text-align: center;
+ }
+ .starter-template .content h1 {
+ margin-bottom: 20px;
+ }
+ .starter-template .links {
+ float: none;
+ text-align: center;
+ margin-top: 60px;
+ }
+ .starter-template .copyright {
+ float: none;
+ text-align: center;
+ }
+}
+@media (max-width: 767px) {
+ .starter-template .content h1 .smaller {
+ font-size: 25px;
+ display: block;
+ }
+ .starter-template .content .lead {
+ font-size: 16px;
+ }
+ .starter-template .links {
+ margin-top: 40px;
+ }
+ .starter-template .links ul li {
+ display: block;
+ margin: 0;
+ }
+ .starter-template .links ul li .icon-muted {
+ display: none;
+ }
+ .starter-template .copyright {
+ margin-top: 20px;
+ }
+}
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/404.jinja2
new file mode 100644
index 000000000..1917f83c7
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/404.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2
index c9b0cec21..ab8c5ea3d 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/layout.jinja2
@@ -1,12 +1,12 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
<title>Alchemy Scaffold for The Pyramid Web Framework</title>
@@ -14,7 +14,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2
new file mode 100644
index 000000000..6b49869c4
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/tests.py b/docs/tutorials/wiki2/src/installation/tutorial/tests.py
new file mode 100644
index 000000000..99e95efd3
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/tests.py
@@ -0,0 +1,65 @@
+import unittest
+import transaction
+
+from pyramid import testing
+
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+class BaseTest(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models')
+ settings = self.config.get_settings()
+
+ from .models import (
+ get_engine,
+ get_session_factory,
+ get_tm_session,
+ )
+
+ self.engine = get_engine(settings)
+ session_factory = get_session_factory(self.engine)
+
+ self.session = get_tm_session(session_factory, transaction.manager)
+
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
+
+ def tearDown(self):
+ from .models.meta import Base
+
+ testing.tearDown()
+ transaction.abort()
+ Base.metadata.drop_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
+
+ def setUp(self):
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
+
+ from .models import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info['one'].name, 'one')
+ self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
+
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/installation/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki2/src/installation/tutorial/views/default.py
index 4cfcae4af..ad0c728d7 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/installation/tutorial/views/default.py
@@ -3,26 +3,25 @@ from pyramid.view import view_config
from sqlalchemy.exc import DBAPIError
-from .models import (
- DBSession,
- MyModel,
- )
+from ..models import MyModel
-@view_config(route_name='home', renderer='templates/mytemplate.pt')
+@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
def my_view(request):
try:
- one = DBSession.query(MyModel).filter(MyModel.name == 'one').first()
+ query = request.dbsession.query(MyModel)
+ one = query.filter(MyModel.name == 'one').first()
except DBAPIError:
- return Response(conn_err_msg, content_type='text/plain', status_int=500)
+ return Response(db_err_msg, content_type='text/plain', status=500)
return {'one': one, 'project': 'tutorial'}
-conn_err_msg = """\
+
+db_err_msg = """\
Pyramid is having a problem using your SQL database. The problem
might be caused by one of the following things:
1. You may need to run the "initialize_tutorial_db" script
- to initialize your database tables. Check your virtual
+ to initialize your database tables. Check your virtual
environment's "bin" directory for this script and try to run it.
2. Your database server may not be running. Check that the
@@ -32,4 +31,3 @@ might be caused by one of the following things:
After you fix the problem, please restart the Pyramid application to
try it again.
"""
-
diff --git a/docs/tutorials/wiki2/src/installation/tutorial/views/notfound.py b/docs/tutorials/wiki2/src/installation/tutorial/views/notfound.py
new file mode 100644
index 000000000..69d6e2804
--- /dev/null
+++ b/docs/tutorials/wiki2/src/installation/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.jinja2')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki2/src/models/MANIFEST.in b/docs/tutorials/wiki2/src/models/MANIFEST.in
index 81beba1b1..42cd299b5 100644
--- a/docs/tutorials/wiki2/src/models/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/models/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt
index 68f430110..5b0101e5f 100644
--- a/docs/tutorials/wiki2/src/models/README.txt
+++ b/docs/tutorials/wiki2/src/models/README.txt
@@ -6,7 +6,7 @@ Getting Started
- cd <directory containing this file>
-- $VENV/bin/python setup.py develop
+- $VENV/bin/pip install -e .
- $VENV/bin/initialize_tutorial_db development.ini
diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini
index a9d53b296..22b733e10 100644
--- a/docs/tutorials/wiki2/src/models/development.ini
+++ b/docs/tutorials/wiki2/src/models/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -27,12 +27,12 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini
index 4684d2f7a..d2ecfe22a 100644
--- a/docs/tutorials/wiki2/src/models/production.ini
+++ b/docs/tutorials/wiki2/src/models/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -6,8 +11,6 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -16,7 +19,10 @@ use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -51,6 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py
index 15e7e5923..742a7c59c 100644
--- a/docs/tutorials/wiki2/src/models/setup.py
+++ b/docs/tutorials/wiki2/src/models/setup.py
@@ -9,8 +9,9 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'bcrypt',
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
@@ -19,16 +20,22 @@ requires = [
'waitress',
]
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
+ ]
+
setup(name='tutorial',
version='0.0',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -36,7 +43,9 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- test_suite='tutorial',
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
entry_points="""\
[paste.app_factory]
diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
index 867049e4f..4dab44823 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
@@ -1,21 +1,12 @@
from pyramid.config import Configurator
-from sqlalchemy import engine_from_config
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.add_route('home', '/')
+ config.include('pyramid_jinja2')
+ config.include('.models')
+ config.include('.routes')
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py
deleted file mode 100644
index f028c917a..000000000
--- a/docs/tutorials/wiki2/src/models/tutorial/models.py
+++ /dev/null
@@ -1,25 +0,0 @@
-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):
- """ The SQLAlchemy declarative model class for a Page object. """
- __tablename__ = 'pages'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- data = Column(Text)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
new file mode 100644
index 000000000..8147052ad
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
@@ -0,0 +1,74 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import configure_mappers
+import zope.sqlalchemy
+
+# import or define all models here to ensure they are attached to the
+# Base.metadata prior to any initialization routines
+from .page import Page # noqa
+from .user import User # noqa
+
+# run configure_mappers after defining all of the models to ensure
+# all relationships can be setup
+configure_mappers()
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_session_factory(engine):
+ factory = sessionmaker()
+ factory.configure(bind=engine)
+ return factory
+
+
+def get_tm_session(session_factory, transaction_manager):
+ """
+ Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
+
+ This function will hook the session to the transaction manager which
+ will take care of committing any changes.
+
+ - When using pyramid_tm it will automatically be committed or aborted
+ depending on whether an exception is raised.
+
+ - When using scripts you should wrap the session in a manager yourself.
+ For example::
+
+ import transaction
+
+ engine = get_engine(settings)
+ session_factory = get_session_factory(engine)
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ """
+ dbsession = session_factory()
+ zope.sqlalchemy.register(
+ dbsession, transaction_manager=transaction_manager)
+ return dbsession
+
+
+def includeme(config):
+ """
+ Initialize the model for a Pyramid app.
+
+ Activate this setup using ``config.include('tutorial.models')``.
+
+ """
+ settings = config.get_settings()
+
+ # use pyramid_tm to hook the transaction lifecycle to the request
+ config.include('pyramid_tm')
+
+ session_factory = get_session_factory(get_engine(settings))
+ config.registry['dbsession_factory'] = session_factory
+
+ # make request.dbsession available for use in Pyramid
+ config.add_request_method(
+ # r.tm is the transaction manager used by pyramid_tm
+ lambda r: get_tm_session(session_factory, r.tm),
+ 'dbsession',
+ reify=True
+ )
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py
new file mode 100644
index 000000000..0682247b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py
@@ -0,0 +1,16 @@
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.schema import MetaData
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/page.py b/docs/tutorials/wiki2/src/models/tutorial/models/page.py
new file mode 100644
index 000000000..74ff1faf8
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/page.py
@@ -0,0 +1,20 @@
+from sqlalchemy import (
+ Column,
+ ForeignKey,
+ Integer,
+ Text,
+)
+from sqlalchemy.orm import relationship
+
+from .meta import Base
+
+
+class Page(Base):
+ """ The SQLAlchemy declarative model class for a Page object. """
+ __tablename__ = 'pages'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ data = Column(Text, nullable=False)
+
+ creator_id = Column(ForeignKey('users.id'), nullable=False)
+ creator = relationship('User', backref='created_pages')
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/user.py b/docs/tutorials/wiki2/src/models/tutorial/models/user.py
new file mode 100644
index 000000000..6fb32a1b2
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/user.py
@@ -0,0 +1,29 @@
+import bcrypt
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+)
+
+from .meta import Base
+
+
+class User(Base):
+ """ The SQLAlchemy declarative model class for a User object. """
+ __tablename__ = 'users'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ role = Column(Text, nullable=False)
+
+ password_hash = Column(Text)
+
+ def set_password(self, pw):
+ pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt())
+ self.password_hash = pwhash
+
+ def check_password(self, pw):
+ if self.password_hash is not None:
+ expected_hash = self.password_hash.encode('utf8')
+ actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash)
+ return expected_hash == actual_hash
+ return False
diff --git a/docs/tutorials/wiki2/src/models/tutorial/routes.py b/docs/tutorials/wiki2/src/models/tutorial/routes.py
new file mode 100644
index 000000000..25504ad4d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/routes.py
@@ -0,0 +1,3 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('home', '/')
diff --git a/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py
index 23a5f13f4..f3c0a6fef 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py
@@ -2,36 +2,56 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import Base
from ..models import (
- DBSession,
- Page,
- Base,
+ get_engine,
+ get_session_factory,
+ get_tm_session,
)
+from ..models import Page, User
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
+ options = parse_vars(argv[2:])
setup_logging(config_uri)
- settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
Base.metadata.create_all(engine)
+
+ session_factory = get_session_factory(engine)
+
with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ editor = User(name='editor', role='editor')
+ editor.set_password('editor')
+ dbsession.add(editor)
+
+ basic = User(name='basic', role='basic')
+ basic.set_password('basic')
+ dbsession.add(basic)
+
+ page = Page(
+ name='FrontPage',
+ creator=editor,
+ data='This is the front page',
+ )
+ dbsession.add(page)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2
new file mode 100644
index 000000000..1917f83c7
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/404.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
new file mode 100644
index 000000000..ab8c5ea3d
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
+
+ <title>Alchemy Scaffold for The Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.7</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
new file mode 100644
index 000000000..6b49869c4
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py
index 57a775e0a..99e95efd3 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py
@@ -3,31 +3,63 @@ import transaction
from pyramid import testing
-from .models import DBSession
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
-class TestMyView(unittest.TestCase):
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- from sqlalchemy import create_engine
- engine = create_engine('sqlite://')
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models')
+ settings = self.config.get_settings()
+
from .models import (
- Base,
- MyModel,
+ get_engine,
+ get_session_factory,
+ get_tm_session,
)
- DBSession.configure(bind=engine)
- Base.metadata.create_all(engine)
- with transaction.manager:
- model = MyModel(name='one', value=55)
- DBSession.add(model)
+
+ self.engine = get_engine(settings)
+ session_factory = get_session_factory(self.engine)
+
+ self.session = get_tm_session(session_factory, transaction.manager)
+
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- DBSession.remove()
+ from .models.meta import Base
+
testing.tearDown()
+ transaction.abort()
+ Base.metadata.drop_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def test_it(self):
- from .views import my_view
- request = testing.DummyRequest()
- info = my_view(request)
+ def setUp(self):
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
+
+ from .models import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
+
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/default.py b/docs/tutorials/wiki2/src/models/tutorial/views/default.py
new file mode 100644
index 000000000..ad0c728d7
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/views/default.py
@@ -0,0 +1,33 @@
+from pyramid.response import Response
+from pyramid.view import view_config
+
+from sqlalchemy.exc import DBAPIError
+
+from ..models import MyModel
+
+
+@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
+def my_view(request):
+ try:
+ query = request.dbsession.query(MyModel)
+ one = query.filter(MyModel.name == 'one').first()
+ except DBAPIError:
+ return Response(db_err_msg, content_type='text/plain', status=500)
+ return {'one': one, 'project': 'tutorial'}
+
+
+db_err_msg = """\
+Pyramid is having a problem using your SQL database. The problem
+might be caused by one of the following things:
+
+1. You may need to run the "initialize_tutorial_db" script
+ to initialize your database tables. Check your virtual
+ environment's "bin" directory for this script and try to run it.
+
+2. Your database server may not be running. Check that the
+ database server referred to by the "sqlalchemy.url" setting in
+ your "development.ini" file is running.
+
+After you fix the problem, please restart the Pyramid application to
+try it again.
+"""
diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/notfound.py b/docs/tutorials/wiki2/src/models/tutorial/views/notfound.py
new file mode 100644
index 000000000..69d6e2804
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.jinja2')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki2/src/tests/MANIFEST.in b/docs/tutorials/wiki2/src/tests/MANIFEST.in
index 81beba1b1..42cd299b5 100644
--- a/docs/tutorials/wiki2/src/tests/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/tests/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt
index 68f430110..5b0101e5f 100644
--- a/docs/tutorials/wiki2/src/tests/README.txt
+++ b/docs/tutorials/wiki2/src/tests/README.txt
@@ -6,7 +6,7 @@ Getting Started
- cd <directory containing this file>
-- $VENV/bin/python setup.py develop
+- $VENV/bin/pip install -e .
- $VENV/bin/initialize_tutorial_db development.ini
diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini
index a9d53b296..4a6c9325c 100644
--- a/docs/tutorials/wiki2/src/tests/development.ini
+++ b/docs/tutorials/wiki2/src/tests/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -17,6 +17,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+auth.secret = seekrit
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
@@ -27,12 +29,12 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -68,4 +70,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini
index 4684d2f7a..a13a0ca19 100644
--- a/docs/tutorials/wiki2/src/tests/production.ini
+++ b/docs/tutorials/wiki2/src/tests/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -6,17 +11,20 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+auth.secret = real-seekrit
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -51,6 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index d8486e462..def3ce1f6 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -9,16 +9,22 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'bcrypt',
+ 'docutils',
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
'transaction',
'zope.sqlalchemy',
'waitress',
- 'docutils',
- 'WebTest', # add this
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
]
setup(name='tutorial',
@@ -26,11 +32,11 @@ setup(name='tutorial',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -38,7 +44,9 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- test_suite='tutorial',
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
entry_points="""\
[paste.app_factory]
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
index cee89184b..f5c033b8b 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
@@ -1,37 +1,13 @@
from pyramid.config import Configurator
-from pyramid.authentication import AuthTktAuthenticationPolicy
-from pyramid.authorization import ACLAuthorizationPolicy
-
-from sqlalchemy import engine_from_config
-
-from tutorial.security import groupfinder
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
- authn_policy = AuthTktAuthenticationPolicy(
- 'sosecret', callback=groupfinder, hashalg='sha512')
- authz_policy = ACLAuthorizationPolicy()
- config = Configurator(settings=settings,
- root_factory='tutorial.models.RootFactory')
- config.include('pyramid_chameleon')
- config.set_authentication_policy(authn_policy)
- config.set_authorization_policy(authz_policy)
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.add_route('view_wiki', '/')
- config.add_route('login', '/login')
- config.add_route('logout', '/logout')
- config.add_route('view_page', '/{pagename}')
- config.add_route('add_page', '/add_page/{pagename}')
- config.add_route('edit_page', '/{pagename}/edit_page')
+ config = Configurator(settings=settings)
+ config.include('pyramid_jinja2')
+ config.include('.models')
+ config.include('.routes')
+ config.include('.security')
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models.py b/docs/tutorials/wiki2/src/tests/tutorial/models.py
deleted file mode 100644
index 4f7e1e024..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/models.py
+++ /dev/null
@@ -1,37 +0,0 @@
-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):
- """ The SQLAlchemy declarative model class for a Page object. """
- __tablename__ = 'pages'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- data = Column(Text)
-
-
-class RootFactory(object):
- __acl__ = [ (Allow, Everyone, 'view'),
- (Allow, 'group:editors', 'edit') ]
- def __init__(self, request):
- pass
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
new file mode 100644
index 000000000..8147052ad
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
@@ -0,0 +1,74 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import configure_mappers
+import zope.sqlalchemy
+
+# import or define all models here to ensure they are attached to the
+# Base.metadata prior to any initialization routines
+from .page import Page # noqa
+from .user import User # noqa
+
+# run configure_mappers after defining all of the models to ensure
+# all relationships can be setup
+configure_mappers()
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_session_factory(engine):
+ factory = sessionmaker()
+ factory.configure(bind=engine)
+ return factory
+
+
+def get_tm_session(session_factory, transaction_manager):
+ """
+ Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
+
+ This function will hook the session to the transaction manager which
+ will take care of committing any changes.
+
+ - When using pyramid_tm it will automatically be committed or aborted
+ depending on whether an exception is raised.
+
+ - When using scripts you should wrap the session in a manager yourself.
+ For example::
+
+ import transaction
+
+ engine = get_engine(settings)
+ session_factory = get_session_factory(engine)
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ """
+ dbsession = session_factory()
+ zope.sqlalchemy.register(
+ dbsession, transaction_manager=transaction_manager)
+ return dbsession
+
+
+def includeme(config):
+ """
+ Initialize the model for a Pyramid app.
+
+ Activate this setup using ``config.include('tutorial.models')``.
+
+ """
+ settings = config.get_settings()
+
+ # use pyramid_tm to hook the transaction lifecycle to the request
+ config.include('pyramid_tm')
+
+ session_factory = get_session_factory(get_engine(settings))
+ config.registry['dbsession_factory'] = session_factory
+
+ # make request.dbsession available for use in Pyramid
+ config.add_request_method(
+ # r.tm is the transaction manager used by pyramid_tm
+ lambda r: get_tm_session(session_factory, r.tm),
+ 'dbsession',
+ reify=True
+ )
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py b/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py
new file mode 100644
index 000000000..0682247b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py
@@ -0,0 +1,16 @@
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.schema import MetaData
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/page.py b/docs/tutorials/wiki2/src/tests/tutorial/models/page.py
new file mode 100644
index 000000000..74ff1faf8
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/page.py
@@ -0,0 +1,20 @@
+from sqlalchemy import (
+ Column,
+ ForeignKey,
+ Integer,
+ Text,
+)
+from sqlalchemy.orm import relationship
+
+from .meta import Base
+
+
+class Page(Base):
+ """ The SQLAlchemy declarative model class for a Page object. """
+ __tablename__ = 'pages'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ data = Column(Text, nullable=False)
+
+ creator_id = Column(ForeignKey('users.id'), nullable=False)
+ creator = relationship('User', backref='created_pages')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/user.py b/docs/tutorials/wiki2/src/tests/tutorial/models/user.py
new file mode 100644
index 000000000..6fb32a1b2
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/user.py
@@ -0,0 +1,29 @@
+import bcrypt
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+)
+
+from .meta import Base
+
+
+class User(Base):
+ """ The SQLAlchemy declarative model class for a User object. """
+ __tablename__ = 'users'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ role = Column(Text, nullable=False)
+
+ password_hash = Column(Text)
+
+ def set_password(self, pw):
+ pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt())
+ self.password_hash = pwhash
+
+ def check_password(self, pw):
+ if self.password_hash is not None:
+ expected_hash = self.password_hash.encode('utf8')
+ actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash)
+ return expected_hash == actual_hash
+ return False
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/routes.py b/docs/tutorials/wiki2/src/tests/tutorial/routes.py
new file mode 100644
index 000000000..f0a8b7f96
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/routes.py
@@ -0,0 +1,56 @@
+from pyramid.httpexceptions import (
+ HTTPNotFound,
+ HTTPFound,
+)
+from pyramid.security import (
+ Allow,
+ Everyone,
+)
+
+from .models import Page
+
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('view_wiki', '/')
+ config.add_route('login', '/login')
+ config.add_route('logout', '/logout')
+ config.add_route('view_page', '/{pagename}', factory=page_factory)
+ config.add_route('add_page', '/add_page/{pagename}',
+ factory=new_page_factory)
+ config.add_route('edit_page', '/{pagename}/edit_page',
+ factory=page_factory)
+
+def new_page_factory(request):
+ pagename = request.matchdict['pagename']
+ if request.dbsession.query(Page).filter_by(name=pagename).count() > 0:
+ next_url = request.route_url('edit_page', pagename=pagename)
+ raise HTTPFound(location=next_url)
+ return NewPage(pagename)
+
+class NewPage(object):
+ def __init__(self, pagename):
+ self.pagename = pagename
+
+ def __acl__(self):
+ return [
+ (Allow, 'role:editor', 'create'),
+ (Allow, 'role:basic', 'create'),
+ ]
+
+def page_factory(request):
+ pagename = request.matchdict['pagename']
+ page = request.dbsession.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ raise HTTPNotFound
+ return PageResource(page)
+
+class PageResource(object):
+ def __init__(self, page):
+ self.page = page
+
+ def __acl__(self):
+ return [
+ (Allow, Everyone, 'view'),
+ (Allow, 'role:editor', 'edit'),
+ (Allow, str(self.page.creator_id), 'edit'),
+ ]
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
index 23a5f13f4..f3c0a6fef 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
@@ -2,36 +2,56 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import Base
from ..models import (
- DBSession,
- Page,
- Base,
+ get_engine,
+ get_session_factory,
+ get_tm_session,
)
+from ..models import Page, User
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
+ options = parse_vars(argv[2:])
setup_logging(config_uri)
- settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
Base.metadata.create_all(engine)
+
+ session_factory = get_session_factory(engine)
+
with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ editor = User(name='editor', role='editor')
+ editor.set_password('editor')
+ dbsession.add(editor)
+
+ basic = User(name='basic', role='basic')
+ basic.set_password('basic')
+ dbsession.add(basic)
+
+ page = Page(
+ name='FrontPage',
+ creator=editor,
+ data='This is the front page',
+ )
+ dbsession.add(page)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security.py b/docs/tutorials/wiki2/src/tests/tutorial/security.py
index d88c9c71f..25cff7b05 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/security.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/security.py
@@ -1,7 +1,40 @@
-USERS = {'editor':'editor',
- 'viewer':'viewer'}
-GROUPS = {'editor':['group:editors']}
+from pyramid.authentication import AuthTktAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+from pyramid.security import (
+ Authenticated,
+ Everyone,
+)
-def groupfinder(userid, request):
- if userid in USERS:
- return GROUPS.get(userid, [])
+from .models import User
+
+
+class MyAuthenticationPolicy(AuthTktAuthenticationPolicy):
+ def authenticated_userid(self, request):
+ user = request.user
+ if user is not None:
+ return user.id
+
+ def effective_principals(self, request):
+ principals = [Everyone]
+ user = request.user
+ if user is not None:
+ principals.append(Authenticated)
+ principals.append(str(user.id))
+ principals.append('role:' + user.role)
+ return principals
+
+def get_user(request):
+ user_id = request.unauthenticated_userid
+ if user_id is not None:
+ user = request.dbsession.query(User).get(user_id)
+ return user
+
+def includeme(config):
+ settings = config.get_settings()
+ authn_policy = MyAuthenticationPolicy(
+ settings['auth.secret'],
+ hashalg='sha512',
+ )
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(ACLAuthorizationPolicy())
+ config.add_request_method(get_user, 'user', reify=True)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css
deleted file mode 100644
index 2f924bcc5..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} \ No newline at end of file
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/404.jinja2
new file mode 100644
index 000000000..37b0a16b6
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/404.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid tutorial wiki</span> <span class="smaller">(based on TurboGears 20-Minute Wiki)</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2
new file mode 100644
index 000000000..7db25c674
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2
@@ -0,0 +1,20 @@
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}Edit {{pagename}} - {% endblock subtitle %}
+
+{% block content %}
+<p>
+Editing <strong>{{pagename}}</strong>
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+<form action="{{ save_url }}" method="post">
+<div class="form-group">
+ <textarea class="form-control" name="body" rows="10" cols="60">{{ pagedata }}</textarea>
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt
deleted file mode 100644
index 50e55c850..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt
+++ /dev/null
@@ -1,74 +0,0 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
-
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
- <![endif]-->
- </head>
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p>
- Editing <strong><span tal:replace="page.name">Page Name Goes
- Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <p class="pull-right">
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
- </p>
- <form action="${save_url}" method="post">
- <div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
- </div>
- <div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
- </div>
- </form>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
- </body>
-</html>
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
index c0c1b6c20..44d14304e 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,31 +22,27 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
- <p>
- Editing <strong><span tal:replace="page.name">Page Name Goes
- Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <form action="${save_url}" method="post">
- <div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
- </div>
- <div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
- </div>
- </form>
+ {% if request.user is none %}
+ <p class="pull-right">
+ <a href="{{ request.route_url('login') }}">Login</a>
+ </p>
+ {% else %}
+ <p class="pull-right">
+ {{request.user.name}} <a href="{{request.route_url('logout')}}">Logout</a>
+ </p>
+ {% endif %}
+ {% block content %}{% endblock %}
</div>
</div>
</div>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2
new file mode 100644
index 000000000..1806de0ff
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2
@@ -0,0 +1,26 @@
+{% extends 'layout.jinja2' %}
+
+{% block title %}Login - {% endblock title %}
+
+{% block content %}
+<p>
+<strong>
+ Login
+</strong><br>
+{{ message }}
+</p>
+<form action="{{ url }}" method="post">
+<input type="hidden" name="next" value="{{ next_url }}">
+<div class="form-group">
+ <label for="login">Username</label>
+ <input type="text" name="login" value="{{ login }}">
+</div>
+<div class="form-group">
+ <label for="password">Password</label>
+ <input type="password" name="password">
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt
deleted file mode 100644
index 5f8e9b98c..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt
+++ /dev/null
@@ -1,54 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>Login - Pyramid tutorial wiki (based on TurboGears
- 20-Minute Wiki)</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon"
- href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/pylons.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/ie6.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top-small">
- <div class="top-small align-center">
- <div>
- <img width="220" height="50" alt="pyramid"
- src="${request.static_url('tutorial:static/pyramid-small.png')}" />
- </div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-right">
- <div id="left" class="app-welcome align-left">
- <b>Login</b><br/>
- <span tal:replace="message"/>
- </div>
- <div id="right" class="app-welcome align-right"></div>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <form action="${url}" method="post">
- <input type="hidden" name="came_from" value="${came_from}"/>
- <input type="text" name="login" value="${login}"/><br/>
- <input type="password" name="password"
- value="${password}"/><br/>
- <input type="submit" name="form.submitted" value="Log In"/>
- </form>
- </div>
- </div>
- </div>
-</body>
-</html>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2
new file mode 100644
index 000000000..94419e228
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2
@@ -0,0 +1,18 @@
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}{{page.name}} - {% endblock subtitle %}
+
+{% block content %}
+<p>{{ content|safe }}</p>
+<p>
+<a href="{{ edit_url }}">
+ Edit this page
+</a>
+</p>
+<p>
+ Viewing <strong>{{page.name}}</strong>, created by <strong>{{page.creator.name}}</strong>.
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests.py b/docs/tutorials/wiki2/src/tests/tutorial/tests.py
deleted file mode 100644
index c50e05b6d..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/tests.py
+++ /dev/null
@@ -1,235 +0,0 @@
-import unittest
-import transaction
-
-from pyramid import testing
-
-
-def _initTestingDB():
- from sqlalchemy import create_engine
- from tutorial.models import (
- DBSession,
- Page,
- Base
- )
- engine = create_engine('sqlite://')
- Base.metadata.create_all(engine)
- DBSession.configure(bind=engine)
- with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
- return DBSession
-
-
-def _registerRoutes(config):
- config.add_route('view_page', '{pagename}')
- config.add_route('edit_page', '{pagename}/edit_page')
- config.add_route('add_page', 'add_page/{pagename}')
-
-
-class ViewWikiTests(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import view_wiki
- return view_wiki(request)
-
- def test_it(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
-
-
-class ViewPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
-
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import view_page
- return view_page(request)
-
- def test_it(self):
- from tutorial.models import Page
- request = testing.DummyRequest()
- request.matchdict['pagename'] = 'IDoExist'
- page = Page(name='IDoExist', data='Hello CruelWorld IDoExist')
- self.session.add(page)
- _registerRoutes(self.config)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(
- info['content'],
- '<div class="document">\n'
- '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
- 'CruelWorld</a> '
- '<a href="http://example.com/IDoExist">'
- 'IDoExist</a>'
- '</p>\n</div>\n')
- self.assertEqual(info['edit_url'],
- 'http://example.com/IDoExist/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
-
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import add_page
- return add_page(request)
-
- def test_it_notsubmitted(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'AnotherPage'}
- info = self._callFUT(request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(info['save_url'],
- 'http://example.com/add_page/AnotherPage')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'AnotherPage'}
- self._callFUT(request)
- page = self.session.query(Page).filter_by(name='AnotherPage').one()
- self.assertEqual(page.data, 'Hello yo!')
-
-
-class EditPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
-
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import edit_page
- return edit_page(request)
-
- def test_it_notsubmitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(info['save_url'],
- 'http://example.com/abc/edit_page')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/abc')
- self.assertEqual(page.data, 'Hello yo!')
-
-
-class FunctionalTests(unittest.TestCase):
-
- viewer_login = '/login?login=viewer&password=viewer' \
- '&came_from=FrontPage&form.submitted=Login'
- viewer_wrong_login = '/login?login=viewer&password=incorrect' \
- '&came_from=FrontPage&form.submitted=Login'
- editor_login = '/login?login=editor&password=editor' \
- '&came_from=FrontPage&form.submitted=Login'
-
- def setUp(self):
- from tutorial import main
- settings = { 'sqlalchemy.url': 'sqlite://'}
- app = main({}, **settings)
- from webtest import TestApp
- self.testapp = TestApp(app)
- _initTestingDB()
-
- def tearDown(self):
- del self.testapp
- from tutorial.models import DBSession
- DBSession.remove()
-
- def test_root(self):
- res = self.testapp.get('/', status=302)
- self.assertEqual(res.location, 'http://localhost/FrontPage')
-
- def test_FrontPage(self):
- res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue(b'FrontPage' in res.body)
-
- def test_unexisting_page(self):
- self.testapp.get('/SomePage', status=404)
-
- def test_successful_log_in(self):
- res = self.testapp.get(self.viewer_login, status=302)
- self.assertEqual(res.location, 'http://localhost/FrontPage')
-
- def test_failed_log_in(self):
- res = self.testapp.get(self.viewer_wrong_login, status=200)
- self.assertTrue(b'login' in res.body)
-
- def test_logout_link_present_when_logged_in(self):
- self.testapp.get(self.viewer_login, status=302)
- res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue(b'Logout' in res.body)
-
- def test_logout_link_not_present_after_logged_out(self):
- self.testapp.get(self.viewer_login, status=302)
- self.testapp.get('/FrontPage', status=200)
- res = self.testapp.get('/logout', status=302)
- self.assertTrue(b'Logout' not in res.body)
-
- def test_anonymous_user_cannot_edit(self):
- res = self.testapp.get('/FrontPage/edit_page', status=200)
- self.assertTrue(b'Login' in res.body)
-
- def test_anonymous_user_cannot_add(self):
- res = self.testapp.get('/add_page/NewPage', status=200)
- self.assertTrue(b'Login' in res.body)
-
- def test_viewer_user_cannot_edit(self):
- self.testapp.get(self.viewer_login, status=302)
- res = self.testapp.get('/FrontPage/edit_page', status=200)
- self.assertTrue(b'Login' in res.body)
-
- def test_viewer_user_cannot_add(self):
- self.testapp.get(self.viewer_login, status=302)
- res = self.testapp.get('/add_page/NewPage', status=200)
- self.assertTrue(b'Login' in res.body)
-
- def test_editors_member_user_can_edit(self):
- self.testapp.get(self.editor_login, status=302)
- res = self.testapp.get('/FrontPage/edit_page', status=200)
- self.assertTrue(b'Editing' in res.body)
-
- def test_editors_member_user_can_add(self):
- self.testapp.get(self.editor_login, status=302)
- res = self.testapp.get('/add_page/NewPage', status=200)
- self.assertTrue(b'Editing' in res.body)
-
- def test_editors_member_user_can_view(self):
- self.testapp.get(self.editor_login, status=302)
- res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue(b'FrontPage' in res.body)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/__init__.py
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
new file mode 100644
index 000000000..715768b2e
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
@@ -0,0 +1,122 @@
+import transaction
+import unittest
+import webtest
+
+
+class FunctionalTests(unittest.TestCase):
+
+ basic_login = (
+ '/login?login=basic&password=basic'
+ '&next=FrontPage&form.submitted=Login')
+ basic_wrong_login = (
+ '/login?login=basic&password=incorrect'
+ '&next=FrontPage&form.submitted=Login')
+ editor_login = (
+ '/login?login=editor&password=editor'
+ '&next=FrontPage&form.submitted=Login')
+
+ @classmethod
+ def setUpClass(cls):
+ from tutorial.models.meta import Base
+ from tutorial.models import (
+ User,
+ Page,
+ get_tm_session,
+ )
+ from tutorial import main
+
+ settings = {
+ 'sqlalchemy.url': 'sqlite://',
+ 'auth.secret': 'seekrit',
+ }
+ app = main({}, **settings)
+ cls.testapp = webtest.TestApp(app)
+
+ session_factory = app.registry['dbsession_factory']
+ cls.engine = session_factory.kw['bind']
+ Base.metadata.create_all(bind=cls.engine)
+
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+ editor = User(name='editor', role='editor')
+ editor.set_password('editor')
+ basic = User(name='basic', role='basic')
+ basic.set_password('basic')
+ page1 = Page(name='FrontPage', data='This is the front page')
+ page1.creator = editor
+ page2 = Page(name='BackPage', data='This is the back page')
+ page2.creator = basic
+ dbsession.add_all([basic, editor, page1, page2])
+
+ @classmethod
+ def tearDownClass(cls):
+ from tutorial.models.meta import Base
+ Base.metadata.drop_all(bind=cls.engine)
+
+ def test_root(self):
+ res = self.testapp.get('/', status=302)
+ self.assertEqual(res.location, 'http://localhost/FrontPage')
+
+ def test_FrontPage(self):
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue(b'FrontPage' in res.body)
+
+ def test_unexisting_page(self):
+ self.testapp.get('/SomePage', status=404)
+
+ def test_successful_log_in(self):
+ res = self.testapp.get(self.basic_login, status=302)
+ self.assertEqual(res.location, 'http://localhost/FrontPage')
+
+ def test_failed_log_in(self):
+ res = self.testapp.get(self.basic_wrong_login, status=200)
+ self.assertTrue(b'login' in res.body)
+
+ def test_logout_link_present_when_logged_in(self):
+ self.testapp.get(self.basic_login, status=302)
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue(b'Logout' in res.body)
+
+ def test_logout_link_not_present_after_logged_out(self):
+ self.testapp.get(self.basic_login, status=302)
+ self.testapp.get('/FrontPage', status=200)
+ res = self.testapp.get('/logout', status=302)
+ self.assertTrue(b'Logout' not in res.body)
+
+ def test_anonymous_user_cannot_edit(self):
+ res = self.testapp.get('/FrontPage/edit_page', status=302).follow()
+ self.assertTrue(b'Login' in res.body)
+
+ def test_anonymous_user_cannot_add(self):
+ res = self.testapp.get('/add_page/NewPage', status=302).follow()
+ self.assertTrue(b'Login' in res.body)
+
+ def test_basic_user_cannot_edit_front(self):
+ self.testapp.get(self.basic_login, status=302)
+ res = self.testapp.get('/FrontPage/edit_page', status=302).follow()
+ self.assertTrue(b'Login' in res.body)
+
+ def test_basic_user_can_edit_back(self):
+ self.testapp.get(self.basic_login, status=302)
+ res = self.testapp.get('/BackPage/edit_page', status=200)
+ self.assertTrue(b'Editing' in res.body)
+
+ def test_basic_user_can_add(self):
+ self.testapp.get(self.basic_login, status=302)
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue(b'Editing' in res.body)
+
+ def test_editors_member_user_can_edit(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/FrontPage/edit_page', status=200)
+ self.assertTrue(b'Editing' in res.body)
+
+ def test_editors_member_user_can_add(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue(b'Editing' in res.body)
+
+ def test_editors_member_user_can_view(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue(b'FrontPage' in res.body)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py
new file mode 100644
index 000000000..2c945ab33
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py
@@ -0,0 +1,168 @@
+import unittest
+import transaction
+
+from pyramid import testing
+
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+class BaseTest(unittest.TestCase):
+ def setUp(self):
+ from ..models import get_tm_session
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('..models')
+ self.config.include('..routes')
+
+ session_factory = self.config.registry['dbsession_factory']
+ self.session = get_tm_session(session_factory, transaction.manager)
+
+ self.init_database()
+
+ def init_database(self):
+ from ..models.meta import Base
+ session_factory = self.config.registry['dbsession_factory']
+ engine = session_factory.kw['bind']
+ Base.metadata.create_all(engine)
+
+ def tearDown(self):
+ testing.tearDown()
+ transaction.abort()
+
+ def makeUser(self, name, role, password='dummy'):
+ from ..models import User
+ user = User(name=name, role=role)
+ user.set_password(password)
+ return user
+
+ def makePage(self, name, data, creator):
+ from ..models import Page
+ return Page(name=name, data=data, creator=creator)
+
+
+class ViewWikiTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+ self.config.include('..routes')
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views.default import view_wiki
+ return view_wiki(request)
+
+ def test_it(self):
+ request = testing.DummyRequest()
+ response = self._callFUT(request)
+ self.assertEqual(response.location, 'http://example.com/FrontPage')
+
+
+class ViewPageTests(BaseTest):
+ def _callFUT(self, request):
+ from tutorial.views.default import view_page
+ return view_page(request)
+
+ def test_it(self):
+ from ..routes import PageResource
+
+ # add a page to the db
+ user = self.makeUser('foo', 'editor')
+ page = self.makePage('IDoExist', 'Hello CruelWorld IDoExist', user)
+ self.session.add_all([page, user])
+
+ # create a request asking for the page we've created
+ request = dummy_request(self.session)
+ request.context = PageResource(page)
+
+ # call the view we're testing and check its behavior
+ info = self._callFUT(request)
+ self.assertEqual(info['page'], page)
+ self.assertEqual(
+ info['content'],
+ '<div class="document">\n'
+ '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
+ 'CruelWorld</a> '
+ '<a href="http://example.com/IDoExist">'
+ 'IDoExist</a>'
+ '</p>\n</div>\n')
+ self.assertEqual(info['edit_url'],
+ 'http://example.com/IDoExist/edit_page')
+
+
+class AddPageTests(BaseTest):
+ def _callFUT(self, request):
+ from tutorial.views.default import add_page
+ return add_page(request)
+
+ def test_it_pageexists(self):
+ from ..models import Page
+ from ..routes import NewPage
+ request = testing.DummyRequest({'form.submitted': True,
+ 'body': 'Hello yo!'},
+ dbsession=self.session)
+ request.user = self.makeUser('foo', 'editor')
+ request.context = NewPage('AnotherPage')
+ self._callFUT(request)
+ pagecount = self.session.query(Page).filter_by(name='AnotherPage').count()
+ self.assertGreater(pagecount, 0)
+
+ def test_it_notsubmitted(self):
+ from ..routes import NewPage
+ request = dummy_request(self.session)
+ request.user = self.makeUser('foo', 'editor')
+ request.context = NewPage('AnotherPage')
+ info = self._callFUT(request)
+ self.assertEqual(info['pagedata'], '')
+ self.assertEqual(info['save_url'],
+ 'http://example.com/add_page/AnotherPage')
+
+ def test_it_submitted(self):
+ from ..models import Page
+ from ..routes import NewPage
+ request = testing.DummyRequest({'form.submitted': True,
+ 'body': 'Hello yo!'},
+ dbsession=self.session)
+ request.user = self.makeUser('foo', 'editor')
+ request.context = NewPage('AnotherPage')
+ self._callFUT(request)
+ page = self.session.query(Page).filter_by(name='AnotherPage').one()
+ self.assertEqual(page.data, 'Hello yo!')
+
+
+class EditPageTests(BaseTest):
+ def _callFUT(self, request):
+ from tutorial.views.default import edit_page
+ return edit_page(request)
+
+ def makeContext(self, page):
+ from ..routes import PageResource
+ return PageResource(page)
+
+ def test_it_notsubmitted(self):
+ user = self.makeUser('foo', 'editor')
+ page = self.makePage('abc', 'hello', user)
+ self.session.add_all([page, user])
+
+ request = dummy_request(self.session)
+ request.context = self.makeContext(page)
+ info = self._callFUT(request)
+ self.assertEqual(info['pagename'], 'abc')
+ self.assertEqual(info['save_url'],
+ 'http://example.com/abc/edit_page')
+
+ def test_it_submitted(self):
+ user = self.makeUser('foo', 'editor')
+ page = self.makePage('abc', 'hello', user)
+ self.session.add_all([page, user])
+
+ request = testing.DummyRequest({'form.submitted': True,
+ 'body': 'Hello yo!'},
+ dbsession=self.session)
+ request.context = self.makeContext(page)
+ response = self._callFUT(request)
+ self.assertEqual(response.location, 'http://example.com/abc')
+ self.assertEqual(page.data, 'Hello yo!')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py
deleted file mode 100644
index 41bea4785..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/views.py
+++ /dev/null
@@ -1,123 +0,0 @@
-import re
-from docutils.core import publish_parts
-
-from pyramid.httpexceptions import (
- HTTPFound,
- HTTPNotFound,
- )
-
-from pyramid.view import (
- view_config,
- forbidden_view_config,
- )
-
-from pyramid.security import (
- remember,
- forget,
- )
-
-from .security import USERS
-
-from .models import (
- DBSession,
- Page,
- )
-
-
-# regular expression used to find WikiWords
-wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-
-@view_config(route_name='view_wiki',
- permission='view')
-def view_wiki(request):
- return HTTPFound(location = request.route_url('view_page',
- pagename='FrontPage'))
-
-@view_config(route_name='view_page', renderer='templates/view.pt',
- permission='view')
-def view_page(request):
- pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).first()
- if page is None:
- return HTTPNotFound('No such page')
-
- def check(match):
- word = match.group(1)
- exists = DBSession.query(Page).filter_by(name=word).all()
- if exists:
- view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, word)
- else:
- add_url = request.route_url('add_page', pagename=word)
- return '<a href="%s">%s</a>' % (add_url, word)
-
- content = publish_parts(page.data, writer_name='html')['html_body']
- content = wikiwords.sub(check, content)
- edit_url = request.route_url('edit_page', pagename=pagename)
- return dict(page=page, content=content, edit_url=edit_url,
- logged_in=request.authenticated_userid)
-
-@view_config(route_name='add_page', renderer='templates/edit.pt',
- permission='edit')
-def add_page(request):
- pagename = request.matchdict['pagename']
- if 'form.submitted' in request.params:
- body = request.params['body']
- page = Page(name=pagename, data=body)
- DBSession.add(page)
- return HTTPFound(location = request.route_url('view_page',
- pagename=pagename))
- save_url = request.route_url('add_page', pagename=pagename)
- page = Page(name='', data='')
- return dict(page=page, save_url=save_url,
- logged_in=request.authenticated_userid)
-
-@view_config(route_name='edit_page', renderer='templates/edit.pt',
- permission='edit')
-def edit_page(request):
- pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).one()
- if 'form.submitted' in request.params:
- page.data = request.params['body']
- DBSession.add(page)
- return HTTPFound(location = request.route_url('view_page',
- pagename=pagename))
- return dict(
- page=page,
- save_url=request.route_url('edit_page', pagename=pagename),
- logged_in=request.authenticated_userid
- )
-
-@view_config(route_name='login', renderer='templates/login.pt')
-@forbidden_view_config(renderer='templates/login.pt')
-def login(request):
- login_url = request.route_url('login')
- referrer = request.url
- if referrer == login_url:
- referrer = '/' # never use the login form itself as came_from
- came_from = request.params.get('came_from', referrer)
- message = ''
- login = ''
- password = ''
- if 'form.submitted' in request.params:
- login = request.params['login']
- password = request.params['password']
- if USERS.get(login) == password:
- headers = remember(request, login)
- return HTTPFound(location = came_from,
- headers = headers)
- message = 'Failed login'
-
- return dict(
- message = message,
- url = request.application_url + '/login',
- came_from = came_from,
- login = login,
- password = password,
- )
-
-@view_config(route_name='logout')
-def logout(request):
- headers = forget(request)
- return HTTPFound(location = request.route_url('view_wiki'),
- headers = headers)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py b/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py
new file mode 100644
index 000000000..2b993b430
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py
@@ -0,0 +1,46 @@
+from pyramid.httpexceptions import HTTPFound
+from pyramid.security import (
+ remember,
+ forget,
+ )
+from pyramid.view import (
+ forbidden_view_config,
+ view_config,
+)
+
+from ..models import User
+
+
+@view_config(route_name='login', renderer='../templates/login.jinja2')
+def login(request):
+ next_url = request.params.get('next', request.referrer)
+ if not next_url:
+ next_url = request.route_url('view_wiki')
+ message = ''
+ login = ''
+ if 'form.submitted' in request.params:
+ login = request.params['login']
+ password = request.params['password']
+ user = request.dbsession.query(User).filter_by(name=login).first()
+ if user is not None and user.check_password(password):
+ headers = remember(request, user.id)
+ return HTTPFound(location=next_url, headers=headers)
+ message = 'Failed login'
+
+ return dict(
+ message=message,
+ url=request.route_url('login'),
+ next_url=next_url,
+ login=login,
+ )
+
+@view_config(route_name='logout')
+def logout(request):
+ headers = forget(request)
+ next_url = request.route_url('view_wiki')
+ return HTTPFound(location=next_url, headers=headers)
+
+@forbidden_view_config()
+def forbidden_view(request):
+ next_url = request.route_url('login', _query={'next': request.url})
+ return HTTPFound(location=next_url)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
new file mode 100644
index 000000000..9358993ea
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
@@ -0,0 +1,64 @@
+import cgi
+import re
+from docutils.core import publish_parts
+
+from pyramid.httpexceptions import HTTPFound
+from pyramid.view import view_config
+
+from ..models import Page
+
+# regular expression used to find WikiWords
+wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
+
+@view_config(route_name='view_wiki')
+def view_wiki(request):
+ next_url = request.route_url('view_page', pagename='FrontPage')
+ return HTTPFound(location=next_url)
+
+@view_config(route_name='view_page', renderer='../templates/view.jinja2',
+ permission='view')
+def view_page(request):
+ page = request.context.page
+
+ def add_link(match):
+ word = match.group(1)
+ exists = request.dbsession.query(Page).filter_by(name=word).all()
+ if exists:
+ view_url = request.route_url('view_page', pagename=word)
+ return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
+ else:
+ add_url = request.route_url('add_page', pagename=word)
+ return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
+
+ content = publish_parts(page.data, writer_name='html')['html_body']
+ content = wikiwords.sub(add_link, content)
+ edit_url = request.route_url('edit_page', pagename=page.name)
+ return dict(page=page, content=content, edit_url=edit_url)
+
+@view_config(route_name='edit_page', renderer='../templates/edit.jinja2',
+ permission='edit')
+def edit_page(request):
+ page = request.context.page
+ if 'form.submitted' in request.params:
+ page.data = request.params['body']
+ next_url = request.route_url('view_page', pagename=page.name)
+ return HTTPFound(location=next_url)
+ return dict(
+ pagename=page.name,
+ pagedata=page.data,
+ save_url=request.route_url('edit_page', pagename=page.name),
+ )
+
+@view_config(route_name='add_page', renderer='../templates/edit.jinja2',
+ permission='create')
+def add_page(request):
+ pagename = request.context.pagename
+ if 'form.submitted' in request.params:
+ body = request.params['body']
+ page = Page(name=pagename, data=body)
+ page.creator = request.user
+ request.dbsession.add(page)
+ next_url = request.route_url('view_page', pagename=pagename)
+ return HTTPFound(location=next_url)
+ save_url = request.route_url('add_page', pagename=pagename)
+ return dict(pagename=pagename, pagedata='', save_url=save_url)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/notfound.py b/docs/tutorials/wiki2/src/tests/tutorial/views/notfound.py
new file mode 100644
index 000000000..69d6e2804
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.jinja2')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki2/src/views/MANIFEST.in b/docs/tutorials/wiki2/src/views/MANIFEST.in
index 81beba1b1..42cd299b5 100644
--- a/docs/tutorials/wiki2/src/views/MANIFEST.in
+++ b/docs/tutorials/wiki2/src/views/MANIFEST.in
@@ -1,2 +1,2 @@
include *.txt *.ini *.cfg *.rst
-recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
+recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.jinja2 *.pt *.txt *.mak *.mako *.js *.html *.xml
diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt
index 68f430110..5b0101e5f 100644
--- a/docs/tutorials/wiki2/src/views/README.txt
+++ b/docs/tutorials/wiki2/src/views/README.txt
@@ -6,7 +6,7 @@ Getting Started
- cd <directory containing this file>
-- $VENV/bin/python setup.py develop
+- $VENV/bin/pip install -e .
- $VENV/bin/initialize_tutorial_db development.ini
diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini
index a9d53b296..22b733e10 100644
--- a/docs/tutorials/wiki2/src/views/development.ini
+++ b/docs/tutorials/wiki2/src/views/development.ini
@@ -1,6 +1,6 @@
###
# app configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
###
[app:main]
@@ -27,12 +27,12 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
# logging configuration
-# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
###
[loggers]
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini
index 4684d2f7a..d2ecfe22a 100644
--- a/docs/tutorials/wiki2/src/views/production.ini
+++ b/docs/tutorials/wiki2/src/views/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -6,8 +11,6 @@ pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
-pyramid.includes =
- pyramid_tm
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
@@ -16,7 +19,10 @@ use = egg:waitress#main
host = 0.0.0.0
port = 6543
-# Begin logging configuration
+###
+# logging configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/narr/logging.html
+###
[loggers]
keys = root, tutorial, sqlalchemy
@@ -51,6 +57,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py
index 09bd63d33..def3ce1f6 100644
--- a/docs/tutorials/wiki2/src/views/setup.py
+++ b/docs/tutorials/wiki2/src/views/setup.py
@@ -9,15 +9,22 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
requires = [
+ 'bcrypt',
+ 'docutils',
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
'transaction',
'zope.sqlalchemy',
'waitress',
- 'docutils',
+ ]
+
+tests_require = [
+ 'WebTest >= 1.3.1', # py3 compat
+ 'pytest', # includes virtualenv
+ 'pytest-cov',
]
setup(name='tutorial',
@@ -25,11 +32,11 @@ setup(name='tutorial',
description='tutorial',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Programming Language :: Python",
- "Framework :: Pyramid",
- "Topic :: Internet :: WWW/HTTP",
- "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
- ],
+ "Programming Language :: Python",
+ "Framework :: Pyramid",
+ "Topic :: Internet :: WWW/HTTP",
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+ ],
author='',
author_email='',
url='',
@@ -37,7 +44,9 @@ setup(name='tutorial',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
- test_suite='tutorial',
+ extras_require={
+ 'testing': tests_require,
+ },
install_requires=requires,
entry_points="""\
[paste.app_factory]
diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
index 37cae1997..4dab44823 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
@@ -1,24 +1,12 @@
from pyramid.config import Configurator
-from sqlalchemy import engine_from_config
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.add_route('view_wiki', '/')
- config.add_route('view_page', '/{pagename}')
- config.add_route('add_page', '/add_page/{pagename}')
- config.add_route('edit_page', '/{pagename}/edit_page')
+ config.include('pyramid_jinja2')
+ config.include('.models')
+ config.include('.routes')
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py
deleted file mode 100644
index f028c917a..000000000
--- a/docs/tutorials/wiki2/src/views/tutorial/models.py
+++ /dev/null
@@ -1,25 +0,0 @@
-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):
- """ The SQLAlchemy declarative model class for a Page object. """
- __tablename__ = 'pages'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- data = Column(Text)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
new file mode 100644
index 000000000..8147052ad
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
@@ -0,0 +1,74 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.orm import configure_mappers
+import zope.sqlalchemy
+
+# import or define all models here to ensure they are attached to the
+# Base.metadata prior to any initialization routines
+from .page import Page # noqa
+from .user import User # noqa
+
+# run configure_mappers after defining all of the models to ensure
+# all relationships can be setup
+configure_mappers()
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_session_factory(engine):
+ factory = sessionmaker()
+ factory.configure(bind=engine)
+ return factory
+
+
+def get_tm_session(session_factory, transaction_manager):
+ """
+ Get a ``sqlalchemy.orm.Session`` instance backed by a transaction.
+
+ This function will hook the session to the transaction manager which
+ will take care of committing any changes.
+
+ - When using pyramid_tm it will automatically be committed or aborted
+ depending on whether an exception is raised.
+
+ - When using scripts you should wrap the session in a manager yourself.
+ For example::
+
+ import transaction
+
+ engine = get_engine(settings)
+ session_factory = get_session_factory(engine)
+ with transaction.manager:
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ """
+ dbsession = session_factory()
+ zope.sqlalchemy.register(
+ dbsession, transaction_manager=transaction_manager)
+ return dbsession
+
+
+def includeme(config):
+ """
+ Initialize the model for a Pyramid app.
+
+ Activate this setup using ``config.include('tutorial.models')``.
+
+ """
+ settings = config.get_settings()
+
+ # use pyramid_tm to hook the transaction lifecycle to the request
+ config.include('pyramid_tm')
+
+ session_factory = get_session_factory(get_engine(settings))
+ config.registry['dbsession_factory'] = session_factory
+
+ # make request.dbsession available for use in Pyramid
+ config.add_request_method(
+ # r.tm is the transaction manager used by pyramid_tm
+ lambda r: get_tm_session(session_factory, r.tm),
+ 'dbsession',
+ reify=True
+ )
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/meta.py b/docs/tutorials/wiki2/src/views/tutorial/models/meta.py
new file mode 100644
index 000000000..0682247b5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/meta.py
@@ -0,0 +1,16 @@
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.schema import MetaData
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.zzzcomputing.com/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/page.py b/docs/tutorials/wiki2/src/views/tutorial/models/page.py
new file mode 100644
index 000000000..74ff1faf8
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/page.py
@@ -0,0 +1,20 @@
+from sqlalchemy import (
+ Column,
+ ForeignKey,
+ Integer,
+ Text,
+)
+from sqlalchemy.orm import relationship
+
+from .meta import Base
+
+
+class Page(Base):
+ """ The SQLAlchemy declarative model class for a Page object. """
+ __tablename__ = 'pages'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ data = Column(Text, nullable=False)
+
+ creator_id = Column(ForeignKey('users.id'), nullable=False)
+ creator = relationship('User', backref='created_pages')
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/user.py b/docs/tutorials/wiki2/src/views/tutorial/models/user.py
new file mode 100644
index 000000000..6fb32a1b2
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/user.py
@@ -0,0 +1,29 @@
+import bcrypt
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+)
+
+from .meta import Base
+
+
+class User(Base):
+ """ The SQLAlchemy declarative model class for a User object. """
+ __tablename__ = 'users'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, nullable=False, unique=True)
+ role = Column(Text, nullable=False)
+
+ password_hash = Column(Text)
+
+ def set_password(self, pw):
+ pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt())
+ self.password_hash = pwhash
+
+ def check_password(self, pw):
+ if self.password_hash is not None:
+ expected_hash = self.password_hash.encode('utf8')
+ actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash)
+ return expected_hash == actual_hash
+ return False
diff --git a/docs/tutorials/wiki2/src/views/tutorial/routes.py b/docs/tutorials/wiki2/src/views/tutorial/routes.py
new file mode 100644
index 000000000..72df58efe
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/routes.py
@@ -0,0 +1,6 @@
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('view_wiki', '/')
+ config.add_route('view_page', '/{pagename}')
+ config.add_route('add_page', '/add_page/{pagename}')
+ config.add_route('edit_page', '/{pagename}/edit_page')
diff --git a/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py
index 23a5f13f4..f3c0a6fef 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py
@@ -2,36 +2,56 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import Base
from ..models import (
- DBSession,
- Page,
- Base,
+ get_engine,
+ get_session_factory,
+ get_tm_session,
)
+from ..models import Page, User
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
+ options = parse_vars(argv[2:])
setup_logging(config_uri)
- settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
Base.metadata.create_all(engine)
+
+ session_factory = get_session_factory(engine)
+
with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
+ dbsession = get_tm_session(session_factory, transaction.manager)
+
+ editor = User(name='editor', role='editor')
+ editor.set_password('editor')
+ dbsession.add(editor)
+
+ basic = User(name='basic', role='basic')
+ basic.set_password('basic')
+ dbsession.add(basic)
+
+ page = Page(
+ name='FrontPage',
+ creator=editor,
+ data='This is the front page',
+ )
+ dbsession.add(page)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/views/tutorial/static/theme.min.css
deleted file mode 100644
index 0d25de5b6..000000000
--- a/docs/tutorials/wiki2/src/views/tutorial/static/theme.min.css
+++ /dev/null
@@ -1 +0,0 @@
-@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/404.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/404.jinja2
new file mode 100644
index 000000000..37b0a16b6
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/404.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid tutorial wiki</span> <span class="smaller">(based on TurboGears 20-Minute Wiki)</span></h1>
+ <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/edit.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/edit.jinja2
new file mode 100644
index 000000000..7db25c674
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/edit.jinja2
@@ -0,0 +1,20 @@
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}Edit {{pagename}} - {% endblock subtitle %}
+
+{% block content %}
+<p>
+Editing <strong>{{pagename}}</strong>
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+<form action="{{ save_url }}" method="post">
+<div class="form-group">
+ <textarea class="form-control" name="body" rows="10" cols="60">{{ pagedata }}</textarea>
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
index 4e5772de0..71785157f 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,36 +22,18 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
- <div tal:replace="structure content">
- Page text goes here.
- </div>
- <p>
- <a tal:attributes="href edit_url" href="">
- Edit this page
- </a>
- </p>
- <p>
- Viewing <strong><span tal:replace="page.name">
- Page Name Goes Here</span></strong>
- </p>
- <p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <p class="pull-right">
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
- </p>
+ {% block content %}{% endblock %}
</div>
</div>
</div>
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt
deleted file mode 100644
index c9b0cec21..000000000
--- a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt
+++ /dev/null
@@ -1,66 +0,0 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
-
- <title>Alchemy Scaffold for The Pyramid Web Framework</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="links">
- <ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
- <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
- <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
- </ul>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
- </body>
-</html>
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/view.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/view.jinja2
new file mode 100644
index 000000000..94419e228
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/view.jinja2
@@ -0,0 +1,18 @@
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}{{page.name}} - {% endblock subtitle %}
+
+{% block content %}
+<p>{{ content|safe }}</p>
+<p>
+<a href="{{ edit_url }}">
+ Edit this page
+</a>
+</p>
+<p>
+ Viewing <strong>{{page.name}}</strong>, created by <strong>{{page.creator.name}}</strong>.
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/views/tutorial/tests.py b/docs/tutorials/wiki2/src/views/tutorial/tests.py
index 9f01d2da5..99e95efd3 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/tests.py
@@ -3,144 +3,63 @@ import transaction
from pyramid import testing
-def _initTestingDB():
- from sqlalchemy import create_engine
- from tutorial.models import (
- DBSession,
- Page,
- Base
- )
- engine = create_engine('sqlite://')
- Base.metadata.create_all(engine)
- DBSession.configure(bind=engine)
- with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
- return DBSession
-
-def _registerRoutes(config):
- config.add_route('view_page', '{pagename}')
- config.add_route('edit_page', '{pagename}/edit_page')
- config.add_route('add_page', 'add_page/{pagename}')
-
-class ViewWikiTests(unittest.TestCase):
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- self.session = _initTestingDB()
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models')
+ settings = self.config.get_settings()
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
+ from .models import (
+ get_engine,
+ get_session_factory,
+ get_tm_session,
+ )
- def _callFUT(self, request):
- from tutorial.views import view_wiki
- return view_wiki(request)
+ self.engine = get_engine(settings)
+ session_factory = get_session_factory(self.engine)
- def test_it(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
+ self.session = get_tm_session(session_factory, transaction.manager)
-class ViewPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import view_page
- return view_page(request)
-
- def test_it(self):
- from tutorial.models import Page
- request = testing.DummyRequest()
- request.matchdict['pagename'] = 'IDoExist'
- page = Page(name='IDoExist', data='Hello CruelWorld IDoExist')
- self.session.add(page)
- _registerRoutes(self.config)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(
- info['content'],
- '<div class="document">\n'
- '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
- 'CruelWorld</a> '
- '<a href="http://example.com/IDoExist">'
- 'IDoExist</a>'
- '</p>\n</div>\n')
- self.assertEqual(info['edit_url'],
- 'http://example.com/IDoExist/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ from .models.meta import Base
- def tearDown(self):
- self.session.remove()
testing.tearDown()
+ transaction.abort()
+ Base.metadata.drop_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def _callFUT(self, request):
- from tutorial.views import add_page
- return add_page(request)
-
- def test_it_notsubmitted(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'AnotherPage'}
- info = self._callFUT(request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(info['save_url'],
- 'http://example.com/add_page/AnotherPage')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'AnotherPage'}
- self._callFUT(request)
- page = self.session.query(Page).filter_by(name='AnotherPage').one()
- self.assertEqual(page.data, 'Hello yo!')
-
-class EditPageTests(unittest.TestCase):
def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
+ from .models import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info['one'].name, 'one')
+ self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
- def _callFUT(self, request):
- from tutorial.views import edit_page
- return edit_page(request)
-
- def test_it_notsubmitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(info['save_url'],
- 'http://example.com/abc/edit_page')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/abc')
- self.assertEqual(page.data, 'Hello yo!')
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py
deleted file mode 100644
index a3707dab5..000000000
--- a/docs/tutorials/wiki2/src/views/tutorial/views.py
+++ /dev/null
@@ -1,72 +0,0 @@
-import cgi
-import re
-from docutils.core import publish_parts
-
-from pyramid.httpexceptions import (
- HTTPFound,
- HTTPNotFound,
- )
-
-from pyramid.view import view_config
-
-from .models import (
- DBSession,
- Page,
- )
-
-# regular expression used to find WikiWords
-wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-
-@view_config(route_name='view_wiki')
-def view_wiki(request):
- return HTTPFound(location = request.route_url('view_page',
- pagename='FrontPage'))
-
-@view_config(route_name='view_page', renderer='templates/view.pt')
-def view_page(request):
- pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).first()
- if page is None:
- return HTTPNotFound('No such page')
-
- def check(match):
- word = match.group(1)
- exists = DBSession.query(Page).filter_by(name=word).all()
- if exists:
- view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
- else:
- add_url = request.route_url('add_page', pagename=word)
- return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
-
- content = publish_parts(page.data, writer_name='html')['html_body']
- content = wikiwords.sub(check, content)
- edit_url = request.route_url('edit_page', pagename=pagename)
- return dict(page=page, content=content, edit_url=edit_url)
-
-@view_config(route_name='add_page', renderer='templates/edit.pt')
-def add_page(request):
- pagename = request.matchdict['pagename']
- if 'form.submitted' in request.params:
- body = request.params['body']
- page = Page(name=pagename, data=body)
- DBSession.add(page)
- return HTTPFound(location = request.route_url('view_page',
- pagename=pagename))
- save_url = request.route_url('add_page', pagename=pagename)
- page = Page(name='', data='')
- return dict(page=page, save_url=save_url)
-
-@view_config(route_name='edit_page', renderer='templates/edit.pt')
-def edit_page(request):
- pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).one()
- if 'form.submitted' in request.params:
- page.data = request.params['body']
- DBSession.add(page)
- return HTTPFound(location = request.route_url('view_page',
- pagename=pagename))
- return dict(
- page=page,
- save_url = request.route_url('edit_page', pagename=pagename),
- )
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/default.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
new file mode 100644
index 000000000..bb6300b75
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
@@ -0,0 +1,73 @@
+import cgi
+import re
+from docutils.core import publish_parts
+
+from pyramid.httpexceptions import (
+ HTTPFound,
+ HTTPNotFound,
+ )
+
+from pyramid.view import view_config
+
+from ..models import Page, User
+
+# regular expression used to find WikiWords
+wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
+
+@view_config(route_name='view_wiki')
+def view_wiki(request):
+ next_url = request.route_url('view_page', pagename='FrontPage')
+ return HTTPFound(location=next_url)
+
+@view_config(route_name='view_page', renderer='../templates/view.jinja2')
+def view_page(request):
+ pagename = request.matchdict['pagename']
+ page = request.dbsession.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ raise HTTPNotFound('No such page')
+
+ def add_link(match):
+ word = match.group(1)
+ exists = request.dbsession.query(Page).filter_by(name=word).all()
+ if exists:
+ view_url = request.route_url('view_page', pagename=word)
+ return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
+ else:
+ add_url = request.route_url('add_page', pagename=word)
+ return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
+
+ content = publish_parts(page.data, writer_name='html')['html_body']
+ content = wikiwords.sub(add_link, content)
+ edit_url = request.route_url('edit_page', pagename=page.name)
+ return dict(page=page, content=content, edit_url=edit_url)
+
+@view_config(route_name='edit_page', renderer='../templates/edit.jinja2')
+def edit_page(request):
+ pagename = request.matchdict['pagename']
+ page = request.dbsession.query(Page).filter_by(name=pagename).one()
+ if 'form.submitted' in request.params:
+ page.data = request.params['body']
+ next_url = request.route_url('view_page', pagename=page.name)
+ return HTTPFound(location=next_url)
+ return dict(
+ pagename=page.name,
+ pagedata=page.data,
+ save_url=request.route_url('edit_page', pagename=page.name),
+ )
+
+@view_config(route_name='add_page', renderer='../templates/edit.jinja2')
+def add_page(request):
+ pagename = request.matchdict['pagename']
+ if request.dbsession.query(Page).filter_by(name=pagename).count() > 0:
+ next_url = request.route_url('edit_page', pagename=pagename)
+ return HTTPFound(location=next_url)
+ if 'form.submitted' in request.params:
+ body = request.params['body']
+ page = Page(name=pagename, data=body)
+ page.creator = (
+ request.dbsession.query(User).filter_by(name='editor').one())
+ request.dbsession.add(page)
+ next_url = request.route_url('view_page', pagename=pagename)
+ return HTTPFound(location=next_url)
+ save_url = request.route_url('add_page', pagename=pagename)
+ return dict(pagename=pagename, pagedata='', save_url=save_url)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/notfound.py b/docs/tutorials/wiki2/src/views/tutorial/views/notfound.py
new file mode 100644
index 000000000..69d6e2804
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/views/notfound.py
@@ -0,0 +1,7 @@
+from pyramid.view import notfound_view_config
+
+
+@notfound_view_config(renderer='../templates/404.jinja2')
+def notfound_view(request):
+ request.response.status = 404
+ return {}
diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst
index 9db95334a..e923ff9cb 100644
--- a/docs/tutorials/wiki2/tests.rst
+++ b/docs/tutorials/wiki2/tests.rst
@@ -1,97 +1,108 @@
+.. _wiki2_adding_tests:
+
============
Adding Tests
============
-We will now add tests for the models and the views and a few functional tests
-in ``tests.py``. Tests ensure that an application works, and that it
-continues to work when changes are made in the future.
+We will now add tests for the models and views as well as a few functional
+tests in a new ``tests`` subpackage. Tests ensure that an application works,
+and that it continues to work when changes are made in the future.
+
+The file ``tests.py`` was generated as part of the ``alchemy`` scaffold, but it
+is a common practice to put tests into a ``tests`` subpackage, especially as
+projects grow in size and complexity. Each module in the test subpackage
+should contain tests for its corresponding module in our application. Each
+corresponding pair of modules should have the same names, except the test
+module should have the prefix ``test_``.
+
+Start by deleting ``tests.py``, then create a new directory to contain our new
+tests as well as a new empty file ``tests/__init__.py``.
-Test the models
-===============
+.. warning::
+
+ It is very important when refactoring a Python module into a package to be
+ sure to delete the cache files (``.pyc`` files or ``__pycache__`` folders)
+ sitting around! Python will prioritize the cache files before traversing
+ into folders, using the old code, and you will wonder why none of your
+ changes are working!
-To test the model class ``Page`` we'll add a new ``PageModelTests`` class to
-our ``tests.py`` file that was generated as part of the ``alchemy`` scaffold.
Test the views
==============
-We'll modify our ``tests.py`` file, adding tests for each view function we
-added previously. As a result, we'll *delete* the ``ViewTests`` class that
-the ``alchemy`` scaffold provided, and add four other test classes:
+We'll create a new ``tests/test_views.py`` file, adding a ``BaseTest`` class
+used as the base for other test classes. Next we'll add tests for each view
+function we previously added to our application. We'll add four test classes:
``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``.
These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page``
views.
+
Functional tests
================
-We'll test the whole application, covering security aspects that are not
-tested in the unit tests, like logging in, logging out, checking that
-the ``viewer`` user cannot add or edit pages, but the ``editor`` user
-can, and so on.
+We'll test the whole application, covering security aspects that are not tested
+in the unit tests, like logging in, logging out, checking that the ``basic``
+user cannot edit pages that it didn't create but the ``editor`` user can, and
+so on.
+
-View the results of all our edits to ``tests.py``
-=================================================
+View the results of all our edits to ``tests`` subpackage
+=========================================================
-Open the ``tutorial/tests.py`` module, and edit it such that it appears as
+Open ``tutorial/tests/test_views.py``, and edit it such that it appears as
follows:
-.. literalinclude:: src/tests/tutorial/tests.py
+.. literalinclude:: src/tests/tutorial/tests/test_views.py
:linenos:
:language: python
-Running the tests
-=================
-
-We can run these tests by using ``setup.py test`` in the same way we did in
-:ref:`running_tests`. However, first we must edit our ``setup.py`` to
-include a dependency on WebTest, which we've used in our ``tests.py``.
-Change the ``requires`` list in ``setup.py`` to include ``WebTest``.
+Open ``tutorial/tests/test_functional.py``, and edit it such that it appears as
+follows:
-.. literalinclude:: src/tests/setup.py
+.. literalinclude:: src/tests/tutorial/tests/test_functional.py
:linenos:
:language: python
- :lines: 11-22
- :emphasize-lines: 11
-After we've added a dependency on WebTest in ``setup.py``, we need to run
-``setup.py develop`` to get WebTest installed into our virtualenv. Assuming
-our shell's current working directory is the "tutorial" distribution
-directory:
-On UNIX:
+.. note::
-.. code-block:: text
-
- $ $VENV/bin/python setup.py develop
+ We're utilizing the excellent WebTest_ package to do functional testing of
+ the application. This is defined in the ``tests_require`` section of our
+ ``setup.py``. Any other dependencies needed only for testing purposes can be
+ added there and will be installed automatically when running
+ ``setup.py test``.
-On Windows:
-
-.. code-block:: text
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
+Running the tests
+=================
-Once that command has completed successfully, we can run the tests
-themselves:
+We can run these tests similarly to how we did in :ref:`running_tests`:
On UNIX:
-.. code-block:: text
+.. code-block:: bash
- $ $VENV/bin/python setup.py test -q
+ $ $VENV/bin/py.test -q
On Windows:
-.. code-block:: text
+.. code-block:: doscon
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py test -q
+ c:\pyramidtut\tutorial> %VENV%\Scripts\py.test -q
The expected result should look like the following:
.. code-block:: text
......................
- ----------------------------------------------------------------------
- Ran 21 tests in 2.700s
+ 22 passed, 1 pytest-warnings in 5.81 seconds
+
+.. note:: If you use Python 3 during this tutorial, you will see deprecation
+ warnings in the output, which we will choose to ignore. In making this
+ tutorial run on both Python 2 and 3, the authors prioritized simplicity and
+ focus for the learner over accommodating warnings. In your own app or as
+ extra credit, you may choose to either drop Python 2 support or hack your
+ code to work without warnings on both Python 2 and 3.
- OK
+.. _webtest: http://docs.pylonsproject.org/projects/webtest/en/latest/
diff --git a/docs/whatsnew-1.0.rst b/docs/whatsnew-1.0.rst
index 9541f0a28..0ed6e21fc 100644
--- a/docs/whatsnew-1.0.rst
+++ b/docs/whatsnew-1.0.rst
@@ -1,4 +1,4 @@
-What's New In Pyramid 1.0
+What's New in Pyramid 1.0
=========================
This article explains the new features in Pyramid version 1.0 as compared to
diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst
index 99737b6d8..a5c7f3393 100644
--- a/docs/whatsnew-1.1.rst
+++ b/docs/whatsnew-1.1.rst
@@ -1,4 +1,4 @@
-What's New In Pyramid 1.1
+What's New in Pyramid 1.1
=========================
This article explains the new features in Pyramid version 1.1 as compared to
diff --git a/docs/whatsnew-1.2.rst b/docs/whatsnew-1.2.rst
index a9fc38908..9ff933ace 100644
--- a/docs/whatsnew-1.2.rst
+++ b/docs/whatsnew-1.2.rst
@@ -1,4 +1,4 @@
-What's New In Pyramid 1.2
+What's New in Pyramid 1.2
=========================
This article explains the new features in :app:`Pyramid` version 1.2 as
diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst
index 2606c3df3..8de69c450 100644
--- a/docs/whatsnew-1.3.rst
+++ b/docs/whatsnew-1.3.rst
@@ -1,4 +1,4 @@
-What's New In Pyramid 1.3
+What's New in Pyramid 1.3
=========================
This article explains the new features in :app:`Pyramid` version 1.3 as
@@ -18,7 +18,7 @@ Python 3 Compatibility
.. image:: python-3.png
Pyramid continues to run on Python 2, but Pyramid is now also Python 3
-compatible. To use Pyramid under Python 3, Python 3.2 or better is required.
+compatible. To use Pyramid under Python 3, Python 3.3 or better is required.
Many Pyramid add-ons are already Python 3 compatible. For example,
``pyramid_debugtoolbar``, ``pyramid_jinja2``, ``pyramid_exclog``,
@@ -523,10 +523,11 @@ Documentation Enhancements
:ref:`making_a_console_script`.
- Removed the "Running Pyramid on Google App Engine" tutorial from the main
- docs. It survives on in the Cookbook
- (http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/deployment/gae.html).
- Rationale: it provides the correct info for the Python 2.5 version of GAE
- only, and this version of Pyramid does not support Python 2.5.
+ docs. It survives on in the Pyramid Community Cookbook as
+ :ref:`Pyramid on Google's App Engine (using appengine-monkey)
+ <cookbook:appengine_tutorial>`. Rationale: it provides the correct info for
+ the Python 2.5 version of GAE only, and this version of Pyramid does not
+ support Python 2.5.
- Updated the :ref:`changing_the_forbidden_view` section, replacing
explanations of registering a view using ``add_view`` or ``view_config``
diff --git a/docs/whatsnew-1.4.rst b/docs/whatsnew-1.4.rst
index 505b9d798..fce889854 100644
--- a/docs/whatsnew-1.4.rst
+++ b/docs/whatsnew-1.4.rst
@@ -1,4 +1,4 @@
-What's New In Pyramid 1.4
+What's New in Pyramid 1.4
=========================
This article explains the new features in :app:`Pyramid` version 1.4 as
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
index 1d863c937..a477ce5ec 100644
--- a/docs/whatsnew-1.5.rst
+++ b/docs/whatsnew-1.5.rst
@@ -1,4 +1,4 @@
-What's New In Pyramid 1.5
+What's New in Pyramid 1.5
=========================
This article explains the new features in :app:`Pyramid` version 1.5 as
@@ -136,6 +136,8 @@ Feature Additions
The feature additions in Pyramid 1.5 follow.
+- Python 3.4 compatibility.
+
- Add ``pdistreport`` script, which prints the Python version in use, the
Pyramid version in use, and the version number and location of all Python
distributions currently installed.
diff --git a/docs/whatsnew-1.6.rst b/docs/whatsnew-1.6.rst
index b99ebeec4..77d89b017 100644
--- a/docs/whatsnew-1.6.rst
+++ b/docs/whatsnew-1.6.rst
@@ -1,40 +1,74 @@
-What's New In Pyramid 1.6
+What's New in Pyramid 1.6
=========================
This article explains the new features in :app:`Pyramid` version 1.6 as
-compared to its predecessor, :app:`Pyramid` 1.5. It also documents backwards
+compared to its predecessor, :app:`Pyramid` 1.5. It also documents backwards
incompatibilities between the two versions and deprecations added to
:app:`Pyramid` 1.6, as well as software dependency changes and notable
documentation additions.
+
Backwards Incompatibilities
---------------------------
+- IPython and BPython support have been removed from pshell in the core. To
+ continue using them on Pyramid 1.6+, you must install the binding packages
+ explicitly. One way to do this is by adding ``pyramid_ipython`` (or
+ ``pyramid_bpython``) to the ``install_requires`` section of your package's
+ ``setup.py`` file, then re-running ``setup.py develop``::
+
+ setup(
+ #...
+ install_requires=[
+ 'pyramid_ipython', # new dependency
+ 'pyramid',
+ #...
+ ],
+ )
+
- ``request.response`` will no longer be mutated when using the
- :func:`~pyramid.renderers.render_to_response` API. It is now necessary
- to pass in
- a ``response=`` argument to :func:`~pyramid.renderers.render_to_response` if
- you wish to supply the renderer with a custom response object for it to
- use. If you do not pass one then a response object will be created using the
- current response factory. Almost all renderers mutate the
- ``request.response`` response object (for example, the JSON renderer sets
- ``request.response.content_type`` to ``application/json``). However, when
- invoking ``render_to_response`` it is not expected that the response object
- being returned would be the same one used later in the request. The response
- object returned from ``render_to_response`` is now explicitly different from
- ``request.response``. This does not change the API of a renderer. See
+ :func:`~pyramid.renderers.render_to_response` API. It is now necessary to
+ pass in a ``response=`` argument to
+ :func:`~pyramid.renderers.render_to_response` if you wish to supply the
+ renderer with a custom response object. If you do not pass one, then a
+ response object will be created using the current response factory. Almost
+ all renderers mutate the ``request.response`` response object (for example,
+ the JSON renderer sets ``request.response.content_type`` to
+ ``application/json``). However, when invoking ``render_to_response``, it is
+ not expected that the response object being returned would be the same one
+ used later in the request. The response object returned from
+ ``render_to_response`` is now explicitly different from ``request.response``.
+ This does not change the API of a renderer. See
https://github.com/Pylons/pyramid/pull/1563
+- In an effort to combat a common issue it is now a
+ :class:`~pyramid.exceptions.ConfigurationError` to register a view
+ callable that is actually an unbound method when using the default view
+ mapper. As unbound methods do not exist in PY3+ possible errors are detected
+ by checking if the first parameter is named ``self``. For example,
+ `config.add_view(ViewClass.some_method, ...)` should actually be
+ `config.add_view(ViewClass, attr='some_method)'`. This was always an issue
+ in Pyramid on PY2 but the backward incompatibility is on PY3+ where you may
+ not use a function with the first parameter named ``self``. In this case
+ it looks too much like a common error and the exception will be raised.
+ See https://github.com/Pylons/pyramid/pull/1498
+
Feature Additions
-----------------
-- Cache busting for static assets has been added and is available via a new
- argument to :meth:`pyramid.config.Configurator.add_static_view`:
- ``cachebust``. Core APIs are shipped for both cache busting via query
- strings and path segments and may be extended to fit into custom asset
- pipelines. See https://github.com/Pylons/pyramid/pull/1380 and
- https://github.com/Pylons/pyramid/pull/1583
+- Python 3.5 and pypy3 compatibility.
+
+- ``pserve --reload`` will no longer crash on syntax errors. See
+ https://github.com/Pylons/pyramid/pull/2044
+
+- Cache busting for static resources has been added and is available via a new
+ :meth:`pyramid.config.Configurator.add_cache_buster` API. Core APIs are
+ shipped for both cache busting via query strings and via asset manifests for
+ integrating into custom asset pipelines. See
+ https://github.com/Pylons/pyramid/pull/1380 and
+ https://github.com/Pylons/pyramid/pull/1583 and
+ https://github.com/Pylons/pyramid/pull/2171
- Assets can now be overidden by an absolute path on the filesystem when using
the :meth:`~pyramid.config.Configurator.override_asset` API. This makes it
@@ -47,99 +81,129 @@ Feature Additions
``config.add_static_view('myapp:static', 'static')`` and
``config.override_asset(to_override='myapp:static/',
override_with='/abs/path/')``. The ``myapp:static`` asset spec is completely
- made up and does not need to exist - it is used for generating urls via
- ``request.static_url('myapp:static/foo.png')``. See
+ made up and does not need to exist—it is used for generating URLs via
+ ``request.static_url('myapp:static/foo.png')``. See
https://github.com/Pylons/pyramid/issues/1252
- Added :meth:`~pyramid.config.Configurator.set_response_factory` and the
``response_factory`` keyword argument to the constructor of
:class:`~pyramid.config.Configurator` for defining a factory that will return
- a custom ``Response`` class. See https://github.com/Pylons/pyramid/pull/1499
+ a custom ``Response`` class. See https://github.com/Pylons/pyramid/pull/1499
-- Add :attr:`pyramid.config.Configurator.root_package` attribute and init
- parameter to assist with includeable packages that wish to resolve
- resources relative to the package in which the configurator was created.
- This is especially useful for addons that need to load asset specs from
- settings, in which case it is may be natural for a developer to define
- imports or assets relative to the top-level package.
- See https://github.com/Pylons/pyramid/pull/1337
+- Added :attr:`pyramid.config.Configurator.root_package` attribute and init
+ parameter to assist with includible packages that wish to resolve resources
+ relative to the package in which the configurator was created. This is
+ especially useful for add-ons that need to load asset specs from settings, in
+ which case it may be natural for a developer to define imports or assets
+ relative to the top-level package. See
+ https://github.com/Pylons/pyramid/pull/1337
-- Overall improvments for the ``proutes`` command. Added ``--format`` and
+- Overall improvements for the ``proutes`` command. Added ``--format`` and
``--glob`` arguments to the command, introduced the ``method``
column for displaying available request methods, and improved the ``view``
- output by showing the module instead of just ``__repr__``.
- See https://github.com/Pylons/pyramid/pull/1488
+ output by showing the module instead of just ``__repr__``. See
+ https://github.com/Pylons/pyramid/pull/1488
- ``pserve`` can now take a ``-b`` or ``--browser`` option to open the server
URL in a web browser. See https://github.com/Pylons/pyramid/pull/1533
-- Support keyword-only arguments and function annotations in views in
- Python 3. See https://github.com/Pylons/pyramid/pull/1556
+- Support keyword-only arguments and function annotations in views in Python 3.
+ See https://github.com/Pylons/pyramid/pull/1556
- The ``append_slash`` argument of
:meth:`~pyramid.config.Configurator.add_notfound_view()` will now accept
anything that implements the :class:`~pyramid.interfaces.IResponse` interface
and will use that as the response class instead of the default
- :class:`~pyramid.httpexceptions.HTTPFound`. See
+ :class:`~pyramid.httpexceptions.HTTPFound`. See
https://github.com/Pylons/pyramid/pull/1610
- The :class:`~pyramid.config.Configurator` has grown the ability to allow
- actions to call other actions during a commit-cycle. This enables much more
+ actions to call other actions during a commit cycle. This enables much more
logic to be placed into actions, such as the ability to invoke other actions
or group them for improved conflict detection. We have also exposed and
- documented the config phases that Pyramid uses in order to further assist in
- building conforming addons. See https://github.com/Pylons/pyramid/pull/1513
+ documented the configuration phases that Pyramid uses in order to further
+ assist in building conforming add-ons. See
+ https://github.com/Pylons/pyramid/pull/1513
- Allow an iterator to be returned from a renderer. Previously it was only
- possible to return bytes or unicode.
- See https://github.com/Pylons/pyramid/pull/1417
+ possible to return bytes or unicode. See
+ https://github.com/Pylons/pyramid/pull/1417
- Improve robustness to timing attacks in the
:class:`~pyramid.authentication.AuthTktCookieHelper` and the
:class:`~pyramid.session.SignedCookieSessionFactory` classes by using the
- stdlib's ``hmac.compare_digest`` if it is available (such as Python 2.7.7+ and
- 3.3+). See https://github.com/Pylons/pyramid/pull/1457
+ stdlib's ``hmac.compare_digest`` if it is available (such as Python 2.7.7+
+ and 3.3+). See https://github.com/Pylons/pyramid/pull/1457
-- Improve the readability of the ``pcreate`` shell script output.
- See https://github.com/Pylons/pyramid/pull/1453
+- Improve the readability of the ``pcreate`` shell script output. See
+ https://github.com/Pylons/pyramid/pull/1453
-- Make it simple to define notfound and forbidden views that wish to use the
- default exception-response view but with altered predicates and other
- configuration options. The ``view`` argument is now optional in
+- Make it simple to define ``notfound`` and ``forbidden`` views that wish to
+ use the default exception-response view, but with altered predicates and
+ other configuration options. The ``view`` argument is now optional in
:meth:`~pyramid.config.Configurator.add_notfound_view` and
:meth:`~pyramid.config.Configurator.add_forbidden_view` See
https://github.com/Pylons/pyramid/issues/494
- The ``pshell`` script will now load a ``PYTHONSTARTUP`` file if one is
- defined in the environment prior to launching the interpreter.
- See https://github.com/Pylons/pyramid/pull/1448
+ defined in the environment prior to launching the interpreter. See
+ https://github.com/Pylons/pyramid/pull/1448
-- Add new HTTP exception objects for status codes
- ``428 Precondition Required``, ``429 Too Many Requests`` and
- ``431 Request Header Fields Too Large`` in ``pyramid.httpexceptions``.
- See https://github.com/Pylons/pyramid/pull/1372/files
+- Add new HTTP exception objects for status codes ``428 Precondition
+ Required``, ``429 Too Many Requests`` and ``431 Request Header Fields Too
+ Large`` in ``pyramid.httpexceptions``. See
+ https://github.com/Pylons/pyramid/pull/1372/files
- ``pcreate`` when run without a scaffold argument will now print information
- on the missing flag, as well as a list of available scaffolds. See
+ on the missing flag, as well as a list of available scaffolds. See
https://github.com/Pylons/pyramid/pull/1566 and
https://github.com/Pylons/pyramid/issues/1297
+- ``pcreate`` will now ask for confirmation if invoked with an argument for a
+ project name that already exists or is importable in the current environment.
+ See https://github.com/Pylons/pyramid/issues/1357 and
+ https://github.com/Pylons/pyramid/pull/1837
+
- Add :func:`pyramid.request.apply_request_extensions` function which can be
used in testing to apply any request extensions configured via
``config.add_request_method``. Previously it was only possible to test the
- extensions by going through Pyramid's router. See
+ extensions by going through Pyramid's router. See
https://github.com/Pylons/pyramid/pull/1581
-
- Make it possible to subclass ``pyramid.request.Request`` and also use
- ``pyramid.request.Request.add_request.method``. See
+ ``pyramid.request.Request.add_request.method``. See
https://github.com/Pylons/pyramid/issues/1529
+- Additional shells for ``pshell`` can now be registered as entry points. See
+ https://github.com/Pylons/pyramid/pull/1891 and
+ https://github.com/Pylons/pyramid/pull/2012
+
+- The variables injected into ``pshell`` are now displayed with their
+ docstrings instead of the default ``str(obj)`` when possible. See
+ https://github.com/Pylons/pyramid/pull/1929
+
+
Deprecations
------------
+- The ``pserve`` command's daemonization features, as well as
+ ``--monitor-restart``, have been deprecated. This includes the
+ ``[start,stop,restart,status]`` subcommands, as well as the ``--daemon``,
+ ``--stop-daemon``, ``--pid-file``, ``--status``, ``--user``, and ``--group``
+ flags. See https://github.com/Pylons/pyramid/pull/2120 and
+ https://github.com/Pylons/pyramid/pull/2189 and
+ https://github.com/Pylons/pyramid/pull/1641
+
+ Please use a real process manager in the future instead of relying on
+ ``pserve`` to daemonize itself. Many options exist, including your operating
+ system's services, such as Systemd or Upstart, as well as Python-based
+ solutions like Circus and Supervisor.
+
+ See https://github.com/Pylons/pyramid/pull/1641 and
+ https://github.com/Pylons/pyramid/pull/2120
+
- The ``principal`` argument to :func:`pyramid.security.remember` was renamed
- to ``userid``. Using ``principal`` as the argument name still works and will
+ to ``userid``. Using ``principal`` as the argument name still works and will
continue to work for the next few releases, but a deprecation warning is
printed.
@@ -150,21 +214,35 @@ Scaffolding Enhancements
- Added line numbers to the log formatters in the scaffolds to assist with
debugging. See https://github.com/Pylons/pyramid/pull/1326
-- Update scaffold generating machinery to return the version of pyramid and
- pyramid docs for use in scaffolds. Updated ``starter``, ``alchemy`` and
- ``zodb`` templates to have links to correctly versioned documentation and
- reflect which pyramid was used to generate the scaffold.
+- Updated scaffold generating machinery to return the version of :app:`Pyramid`
+ and its documentation for use in scaffolds. Updated ``starter``, ``alchemy``
+ and ``zodb`` templates to have links to correctly versioned documentation,
+ and to reflect which :app:`Pyramid` was used to generate the scaffold.
+
+- Removed non-ASCII copyright symbol from templates, as this was causing the
+ scaffolds to fail for project generation.
-- Removed non-ascii copyright symbol from templates, as this was
- causing the scaffolds to fail for project generation.
Documentation Enhancements
--------------------------
-- Removed logging configuration from Quick Tutorial ini files except for
- scaffolding- and logging-related chapters to avoid needing to explain it too
+- Removed logging configuration from Quick Tutorial ``ini`` files, except for
+ scaffolding- and logging-related chapters, to avoid needing to explain it too
early.
-- Improve and clarify the documentation on what Pyramid defines as a
- ``principal`` and a ``userid`` in its security APIs.
- See https://github.com/Pylons/pyramid/pull/1399
+- Improve and clarify the documentation on what :app:`Pyramid` defines as a
+ ``principal`` and a ``userid`` in its security APIs. See
+ https://github.com/Pylons/pyramid/pull/1399
+
+- Moved the documentation for ``accept`` on
+ :meth:`pyramid.config.Configurator.add_view` to no longer be part of the
+ predicate list. See https://github.com/Pylons/pyramid/issues/1391 for a bug
+ report stating ``not_`` was failing on ``accept``. Discussion with @mcdonc
+ led to the conclusion that it should not be documented as a predicate.
+ See https://github.com/Pylons/pyramid/pull/1487 for this PR.
+
+- Clarify a previously-implied detail of the ``ISession.invalidate`` API
+ documentation.
+
+- Add documentation of command line programs (``p*`` scripts). See
+ https://github.com/Pylons/pyramid/pull/2191
diff --git a/docs/whatsnew-1.7.rst b/docs/whatsnew-1.7.rst
new file mode 100644
index 000000000..398b12f01
--- /dev/null
+++ b/docs/whatsnew-1.7.rst
@@ -0,0 +1,190 @@
+What's New in Pyramid 1.7
+=========================
+
+This article explains the new features in :app:`Pyramid` version 1.7 as
+compared to its predecessor, :app:`Pyramid` 1.6. It also documents backwards
+incompatibilities between the two versions and deprecations added to
+:app:`Pyramid` 1.7, as well as software dependency changes and notable
+documentation additions.
+
+Backwards Incompatibilities
+---------------------------
+
+- The default hash algorithm for
+ :class:`pyramid.authentication.AuthTktAuthenticationPolicy` has changed from
+ ``md5`` to ``sha512``. If you are using the authentication policy and need to
+ continue using ``md5``, please explicitly set ``hashalg='md5'``.
+
+ If you are not currently specifying the ``hashalg`` option in your apps, then
+ this change means any existing auth tickets (and associated cookies) will no
+ longer be valid, users will be logged out, and have to login to their
+ accounts again.
+
+ This change has been issuing a DeprecationWarning since :app:`Pyramid` 1.4.
+
+ See https://github.com/Pylons/pyramid/pull/2496
+
+- Python 2.6 and 3.2 are no longer supported by Pyramid. See
+ https://github.com/Pylons/pyramid/issues/2368 and
+ https://github.com/Pylons/pyramid/pull/2256
+
+- The :func:`pyramid.session.check_csrf_token` function no longer validates a
+ csrf token in the query string of a request. Only headers and request bodies
+ are supported. See https://github.com/Pylons/pyramid/pull/2500
+
+- A global permission set via
+ :meth:`pyramid.config.Configurator.set_default_permission` will no longer
+ affect exception views. A permission must be set explicitly on the view for
+ it to be enforced. See https://github.com/Pylons/pyramid/pull/2534
+
+Feature Additions
+-----------------
+
+- A new :ref:`view_derivers` concept has been added to Pyramid to allow
+ framework authors to inject elements into the standard Pyramid view pipeline
+ and affect all views in an application. This is similar to a decorator except
+ that it has access to options passed to ``config.add_view`` and can affect
+ other stages of the pipeline such as the raw response from a view or prior
+ to security checks. See https://github.com/Pylons/pyramid/pull/2021
+
+- Added a ``require_csrf`` view option which will enforce CSRF checks on
+ requests with an unsafe method as defined by RFC2616. If the CSRF check fails
+ a ``BadCSRFToken`` exception will be raised and may be caught by exception
+ views (the default response is a ``400 Bad Request``). This option should be
+ used in place of the deprecated ``check_csrf`` view predicate which would
+ normally result in unexpected ``404 Not Found`` response to the client
+ instead of a catchable exception. See :ref:`auto_csrf_checking`,
+ https://github.com/Pylons/pyramid/pull/2413 and
+ https://github.com/Pylons/pyramid/pull/2500
+
+- Added a new method,
+ :meth:`pyramid.config.Configurator.set_csrf_default_options`,
+ for configuring CSRF checks used by the ``require_csrf=True`` view option.
+ This method can be used to turn on CSRF checks globally for every view
+ in the application. This should be considered a good default for websites
+ built on Pyramid. It is possible to opt-out of CSRF checks on a per-view
+ basis by setting ``require_csrf=False`` on those views.
+ See :ref:`auto_csrf_checking` and
+ https://github.com/Pylons/pyramid/pull/2413 and
+ https://github.com/Pylons/pyramid/pull/2518
+
+- Added an additional CSRF validation that checks the origin/referrer of a
+ request and makes sure it matches the current ``request.domain``. This
+ particular check is only active when accessing a site over HTTPS as otherwise
+ browsers don't always send the required information. If this additional CSRF
+ validation fails a ``BadCSRFOrigin`` exception will be raised and may be
+ caught by exception views (the default response is ``400 Bad Request``).
+ Additional allowed origins may be configured by setting
+ ``pyramid.csrf_trusted_origins`` to a list of domain names (with ports if on
+ a non standard port) to allow. Subdomains are not allowed unless the domain
+ name has been prefixed with a ``.``. See
+ https://github.com/Pylons/pyramid/pull/2501
+
+- Added a new :func:`pyramid.session.check_csrf_origin` API for validating the
+ origin or referrer headers against the request's domain.
+ See https://github.com/Pylons/pyramid/pull/2501
+
+- Subclasses of :class:`pyramid.httpexceptions.HTTPException` will now take
+ into account the best match for the clients ``Accept`` header, and depending
+ on what is requested will return ``text/html``, ``application/json`` or
+ ``text/plain``. The default for ``*/*`` is still ``text/html``, but if
+ ``application/json`` is explicitly mentioned it will now receive a valid
+ JSON response. See https://github.com/Pylons/pyramid/pull/2489
+
+- A new event, :class:`pyramid.events.BeforeTraversal`, and interface
+ :class:`pyramid.interfaces.IBeforeTraversal` have been introduced that will
+ notify listeners before traversal starts in the router.
+ See :ref:`router_chapter` as well as
+ https://github.com/Pylons/pyramid/pull/2469 and
+ https://github.com/Pylons/pyramid/pull/1876
+
+- A new method, :meth:`pyramid.request.Request.invoke_exception_view`, which
+ can be used to invoke an exception view and get back a response. This is
+ useful for rendering an exception view outside of the context of the
+ ``EXCVIEW`` tween where you may need more control over the request.
+ See https://github.com/Pylons/pyramid/pull/2393
+
+- A global permission set via
+ :meth:`pyramid.config.Configurator.set_default_permission` will no longer
+ affect exception views. A permission must be set explicitly on the view for
+ it to be enforced. See https://github.com/Pylons/pyramid/pull/2534
+
+- Allow a leading ``=`` on the key of the request param predicate.
+ For example, ``'=abc=1'`` is equivalent down to
+ ``request.params['=abc'] == '1'``.
+ See https://github.com/Pylons/pyramid/pull/1370
+
+- Allow using variable substitutions like ``%(LOGGING_LOGGER_ROOT_LEVEL)s``
+ for logging sections of the .ini file and populate these variables from
+ the ``pserve`` command line -- e.g.:
+
+ ``pserve development.ini LOGGING_LOGGER_ROOT_LEVEL=DEBUG``
+
+ This support is thanks to the new ``global_conf`` option on
+ :func:`pyramid.paster.setup_logging`.
+ See https://github.com/Pylons/pyramid/pull/2399
+
+- The :attr:`pyramid.tweens.EXCVIEW` tween will now re-raise the original
+ exception if no exception view could be found to handle it. This allows
+ the exception to be handled upstream by another tween or middelware.
+ See https://github.com/Pylons/pyramid/pull/2567
+
+Deprecations
+------------
+
+- The ``check_csrf`` view predicate has been deprecated. Use the
+ new ``require_csrf`` option or the ``pyramid.require_default_csrf`` setting
+ to ensure that the :class:`pyramid.exceptions.BadCSRFToken` exception is
+ raised. See https://github.com/Pylons/pyramid/pull/2413
+
+- Support for Python 3.3 will be removed in Pyramid 1.8.
+ https://github.com/Pylons/pyramid/issues/2477
+
+Scaffolding Enhancements
+------------------------
+
+- A complete overhaul of the ``alchemy`` scaffold to show more modern best
+ practices with regards to SQLAlchemy session management, as well as a more
+ modular approach to configuration, separating routes into a separate module
+ to illustrate uses of :meth:`pyramid.config.Configurator.include`.
+ See https://github.com/Pylons/pyramid/pull/2024
+
+Documentation Enhancements
+--------------------------
+
+A massive overhaul of the packaging and tools used in the documentation
+was completed in https://github.com/Pylons/pyramid/pull/2468. A summary
+follows:
+
+- All docs now recommend using ``pip`` instead of ``easy_install``.
+
+- The installation docs now expect the user to be using Python 3.4 or
+ greater with access to the ``python3 -m venv`` tool to create virtual
+ environments.
+
+- Tutorials now use ``py.test`` and ``pytest-cov`` instead of ``nose`` and
+ ``coverage``.
+
+- Further updates to the scaffolds as well as tutorials and their src files.
+
+Along with the overhaul of the ``alchemy`` scaffold came a total overhaul
+of the :ref:`bfg_sql_wiki_tutorial` tutorial to introduce more modern
+features into the usage of SQLAlchemy with Pyramid and provide a better
+starting point for new projects. See
+https://github.com/Pylons/pyramid/pull/2024 for more. Highlights were:
+
+- New SQLAlchemy session management without any global ``DBSession``. Replaced
+ by a per-request ``request.dbsession`` property.
+
+- A new authentication chapter demonstrating how to get simple authentication
+ bootstrapped quickly in an application.
+
+- Authorization was overhauled to show the use of per-route context factories
+ which demonstrate object-level authorization on top of simple group-level
+ authorization. Did you want to restrict page edits to only the owner but
+ couldn't figure it out before? Here you go!
+
+- The users and groups are stored in the database now instead of within
+ tutorial-specific global variables.
+
+- User passwords are stored using ``bcrypt``.