diff options
Diffstat (limited to 'docs')
253 files changed, 8572 insertions, 10469 deletions
diff --git a/docs/Makefile b/docs/Makefile index 3d706d17e..1d032cf45 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -25,7 +25,7 @@ help: clean: -rm -rf _build/* -html: _themes/ +html: _themes mkdir -p _build/html _build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo diff --git a/docs/_static/latex-note.png b/docs/_static/latex-note.png Binary files differindex 7e1647837..95d00a973 100644 --- a/docs/_static/latex-note.png +++ b/docs/_static/latex-note.png diff --git a/docs/_static/latex-warning.png b/docs/_static/latex-warning.png Binary files differindex 747b24529..6707bc362 100644 --- a/docs/_static/latex-warning.png +++ b/docs/_static/latex-warning.png diff --git a/docs/api.rst b/docs/api.rst index b650c8ded..be7942502 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -24,7 +24,6 @@ documentation is organized alphabetically by module name. api/renderers api/request api/response - api/router api/scripting api/security api/session diff --git a/docs/api/authentication.rst b/docs/api/authentication.rst index 54db77417..a6d4c1e18 100644 --- a/docs/api/authentication.rst +++ b/docs/api/authentication.rst @@ -3,6 +3,9 @@ :mod:`pyramid.authentication` -------------------------------- +Authentication Policies +~~~~~~~~~~~~~~~~~~~~~~~ + .. automodule:: pyramid.authentication .. autoclass:: AuthTktAuthenticationPolicy @@ -11,3 +14,10 @@ .. autoclass:: RemoteUserAuthenticationPolicy +Helper Classes +~~~~~~~~~~~~~~ + + .. autoclass:: AuthTktCookieHelper + + + diff --git a/docs/api/config.rst b/docs/api/config.rst index 3f37e739c..4b5f1fa21 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -28,6 +28,8 @@ .. automethod:: include + .. automethod:: add_directive + .. automethod:: with_package .. automethod:: maybe_dotted @@ -48,14 +50,10 @@ .. automethod:: add_translation_dirs - .. automethod:: add_handler - .. automethod:: add_view .. automethod:: derive_view - .. automethod:: load_zcml(spec) - .. automethod:: make_wsgi_app() .. automethod:: override_asset(to_override, override_with) diff --git a/docs/api/i18n.rst b/docs/api/i18n.rst index 0c2f8b86e..53e8c8a9b 100644 --- a/docs/api/i18n.rst +++ b/docs/api/i18n.rst @@ -24,6 +24,8 @@ .. autofunction:: default_locale_negotiator + .. autofunction:: make_localizer + See :ref:`i18n_chapter` for more information about using :app:`Pyramid` internationalization and localization services within an application. diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index b3c14e5f7..3ce926230 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -35,3 +35,7 @@ Other Interfaces .. autointerface:: ITemplateRenderer + .. autointerface:: IViewMapperFactory + + .. autointerface:: IViewMapper + diff --git a/docs/api/registry.rst b/docs/api/registry.rst index 5f61b1122..4d327370a 100644 --- a/docs/api/registry.rst +++ b/docs/api/registry.rst @@ -9,6 +9,8 @@ .. attribute:: settings - The :term:`deployment settings` object. See :ref:`deployment_settings` - for information. + The dictionary-like :term:`deployment settings` object. See + :ref:`deployment_settings` for information. This object is often + accessed as ``request.registry.settings`` or + ``config.registry.settings`` in a typical Pyramid application. diff --git a/docs/api/request.rst b/docs/api/request.rst index 13b434a51..d17441c0a 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -113,3 +113,24 @@ request, the value of this attribute will be ``None``. See :ref:`matched_route`. + .. automethod:: add_response_callback + + .. automethod:: add_finished_callback + + .. automethod:: route_url + + .. automethod:: route_path + + .. automethod:: resource_url + + .. automethod:: static_url + + .. attribute:: response_* + + You can set attributes on a :class:`pyramid.request.Request` which will + influence the behavor of *rendered* responses (views which use a + :term:`renderer` and which don't directly return a response). These + attributes begin with ``response_``, such as ``response_headerlist``. If + you need to influence response values from a view that uses a renderer + (such as the status code, a header, the content type, etc) see, + :ref:`response_request_attrs`. diff --git a/docs/api/router.rst b/docs/api/router.rst deleted file mode 100644 index e5ffe97ce..000000000 --- a/docs/api/router.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _router_module: - -:mod:`pyramid.router` ---------------------- - -.. automodule:: pyramid.router - -.. autofunction:: pyramid.router.make_app(root_factory, package=None, filename='configure.zcml', settings=None) diff --git a/docs/api/security.rst b/docs/api/security.rst index 4acf5fe4d..de249355d 100644 --- a/docs/api/security.rst +++ b/docs/api/security.rst @@ -10,6 +10,8 @@ Authentication API Functions .. autofunction:: authenticated_userid +.. autofunction:: unauthenticated_userid + .. autofunction:: effective_principals .. autofunction:: forget diff --git a/docs/api/testing.rst b/docs/api/testing.rst index fdcdadee8..f388dc263 100644 --- a/docs/api/testing.rst +++ b/docs/api/testing.rst @@ -5,26 +5,6 @@ .. automodule:: pyramid.testing - .. autofunction:: registerDummySecurityPolicy - - .. autofunction:: registerResources - - .. autofunction:: registerEventListener - - .. autofunction:: registerTemplateRenderer - - .. autofunction:: registerView - - .. autofunction:: registerUtility - - .. autofunction:: registerAdapter - - .. autofunction:: registerSubscriber - - .. autofunction:: registerRoute - - .. autofunction:: registerSettings - .. autofunction:: setUp .. autofunction:: tearDown diff --git a/docs/api/url.rst b/docs/api/url.rst index 1aa3082b7..01be76283 100644 --- a/docs/api/url.rst +++ b/docs/api/url.rst @@ -9,6 +9,8 @@ .. autofunction:: route_url + .. autofunction:: current_route_url + .. autofunction:: route_path .. autofunction:: static_url diff --git a/docs/api/view.rst b/docs/api/view.rst index 0057cca4a..4dddea25f 100644 --- a/docs/api/view.rst +++ b/docs/api/view.rst @@ -18,6 +18,7 @@ .. autoclass:: static :members: + :inherited-members: .. autofunction:: append_slash_notfound_view(context, request) diff --git a/docs/authorintro.rst b/docs/authorintro.rst index f5ac402b0..38152fb99 100644 --- a/docs/authorintro.rst +++ b/docs/authorintro.rst @@ -54,7 +54,7 @@ technologies. Book Content ============ -This book is divided into four major parts: +This book is divided into three major parts: :ref:`narrative_documentation` @@ -79,12 +79,6 @@ This book is divided into four major parts: :app:`Pyramid`. The API documentation is organized alphabetically by module name. -:ref:`zcml_reference` - - Comprehensive reference material for every :term:`ZCML directive` - provided by :app:`Pyramid`. The ZCML directive documentation is - organized alphabetically by directive name. - .. index:: single: repoze.zope2 single: Zope 3 @@ -163,6 +157,13 @@ others' technology. single: Holth, Daniel single: Hathaway, Shane single: Akkerman, Wichert + single: Laflamme, Blaise + single: Laflamme, Hugues + single: Bangert, Ben + single: Duncan, Casey + single: Orr, Mike + single: Shipman, John + single: Beelby, Chris Thanks ====== @@ -173,11 +174,11 @@ typewriter (a Royal), and my mother, who bought me my first computer Thanks to the following people for providing expertise, resources, and software. Without the help of these folks, neither this book nor the -software which it details would exist: Paul Everitt, Tres Seaver, -Andrew Sawyers, Malthe Borch, Carlos de la Guardia, Chris Rossi, Shane -Hathaway, Daniel Holth, Wichert Akkerman, Georg Brandl, Simon Oram and -Nat Hardwick of Electrosoup, Ian Bicking of the Open Planning Project, -Jim Fulton of Zope Corporation, Tom Moroz of the Open Society +software which it details would exist: Paul Everitt, Tres Seaver, Andrew +Sawyers, Malthe Borch, Carlos de la Guardia, Chris Rossi, Shane Hathaway, +Daniel Holth, Wichert Akkerman, Georg Brandl, Blaise Laflamme, Ben Bangert, +Casey Duncan, Hugues Laflamme, Mike Orr, John Shipman, Chris Beelby, Simon +Oram, Nat Hardwick, Ian Bicking, Jim Fulton, Tom Moroz of the Open Society Institute, and Todd Koym of Environmental Health Sciences. Thanks to Guido van Rossum and Tim Peters for Python. diff --git a/docs/build_book b/docs/build_book deleted file mode 100755 index d79de4e59..000000000 --- a/docs/build_book +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -BOOK=1 make clean latex -cd _build/latex -make all diff --git a/docs/conf.py b/docs/conf.py index 7bcdf3a07..a987106d4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,7 @@ import sys, os import datetime +import inspect import warnings warnings.simplefilter('ignore', DeprecationWarning) @@ -56,8 +57,24 @@ for item in os.listdir(parent): # 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.autodoc', 'sphinx.ext.doctest', - 'repoze.sphinx.autointerface'] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'repoze.sphinx.autointerface', +# 'sphinx.ext.intersphinx' + ] + +# Looks for objects in other Pyramid projects +## intersphinx_mapping = { +## 'cookbook': +## ('http://docs.pylonsproject.org/projects/pyramid_cookbook/dev/', None), +## 'handlers': +## ('http://docs.pylonsproject.org/projects/pyramid_handlers/dev/', None), +## 'zcml': +## ('http://docs.pylonsproject.org/projects/pyramid_zcml/dev/', None), +## 'jinja2': +## ('http://docs.pylonsproject.org/projects/pyramid_jinja2/dev/', None), +## } # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -76,7 +93,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.0a7' +version = '1.0' # The full version, including alpha/beta/rc tags. release = version @@ -407,6 +424,18 @@ def setup(app): app.add_directive('frontmatter', frontmatter, 1, (0, 0, 0)) app.add_directive('mainmatter', mainmatter, 1, (0, 0, 0)) app.add_directive('backmatter', backmatter, 1, (0, 0, 0)) + app.connect('autodoc-process-signature', resig) + +def resig(app, what, name, obj, options, signature, return_annotation): + """ Allow for preservation of ``@action_method`` decorated methods + in configurator """ + docobj = getattr(obj, '__docobj__', None) + if docobj is not None: + argspec = inspect.getargspec(docobj) + if argspec[0] and argspec[0][0] in ('cls', 'self'): + del argspec[0][0] + signature = inspect.formatargspec(*argspec) + return signature, return_annotation # turn off all line numbers in latex formatting @@ -426,7 +455,7 @@ def setup(app): epub_title = 'The Pyramid Web Application Development Framework, Version 1.0' epub_author = 'Chris McDonough' epub_publisher = 'Agendaless Consulting' -epub_copyright = '2008-2010' +epub_copyright = '2008-2011' # The language of the text. It defaults to the language option # or en if the language is not set. @@ -437,7 +466,7 @@ epub_scheme = 'ISBN' # The unique identifier of the text. This can be a ISBN number # or the project homepage. -epub_identifier = '0615345379' +epub_identifier = '0615445675' # A unique identification for the text. epub_uid = 'The Pyramid Web Application Development Framework, Version 1.0' diff --git a/docs/conventions.rst b/docs/conventions.rst index 71c40e104..9e8510e4d 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://pylonshq.com>`_ + `Pylons <http://pylonsproject.org>`_ References to sections and chapters are presented using the following style: diff --git a/docs/copyright.rst b/docs/copyright.rst index fa564a785..64f84879a 100644 --- a/docs/copyright.rst +++ b/docs/copyright.rst @@ -7,13 +7,13 @@ by Chris McDonough .. |copy| unicode:: U+000A9 .. COPYRIGHT SIGN -Copyright |copy| 2008-2010, Agendaless Consulting. +Copyright |copy| 2008-2011, Agendaless Consulting. -.. ISBN-10: 0615345379 +ISBN-10: 0615445675 -.. ISBN-13: 978-0-615-34537-6 +ISBN-13: 978-0615445670 -.. First print publishing: February, 2010 +First print publishing: February, 2011 All rights reserved. This material may be copied or distributed only subject to the terms and conditions set forth in the `Creative Commons @@ -48,23 +48,32 @@ with respect to the use of the information contained herein. Attributions ------------ +Editor: + Casey Duncan + Contributors: - Ben Bangert, Blaise Laflamme, Carlos de la Guardia, Paul Everitt, - Marius Gedminas + Ben Bangert, Blaise Laflamme, Rob Miller, Mike Orr, Carlos de la Guardia, + Paul Everitt, Tres Seaver, John Shipman, Marius Gedminas, Chris Rossi, + Joachim Krebs, Xavier Spriet, Reed O'Brien, William Chambers, Charlie + Choiniere, Jamaludin Ahmad, Graham Higgins. -.. Cover Designer: -.. Nat Hardwick of `Electrosoup <http://www.electrosoup.co.uk>`_. +Cover Designer: + Hugues Laflamme of `Kemeneur <http://www.kemeneur.com/>`_. Used with permission: + The :ref:`webob_chapter` chapter is adapted, with permission, from documentation originally written by Ian Bicking. -.. Print Production -.. ---------------- + The :ref:`much_ado_about_traversal_chapter` chapter is adapted, + with permission, from an article written by Rob Miller. + +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. +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. Contacting The Publisher ------------------------ @@ -79,7 +88,7 @@ HTML Version and Source Code ---------------------------- An HTML version of this book is freely available via -http://docs.pylonshq.com +http://docs.pylonsproject.org The source code for the examples used in this book are available within the :app:`Pyramid` software distribution, always available diff --git a/docs/coversizing.py b/docs/coversizing.py new file mode 100644 index 000000000..054a741eb --- /dev/null +++ b/docs/coversizing.py @@ -0,0 +1,19 @@ +# see https://www.createspace.com/Products/Book/#content4 +# https://www.createspace.com/Help/Index.jsp?orgId=00D300000001Sh9&id=50170000000I7be + +page_count = 600 +bleed = .125 +spine_width = .002252 * page_count +trim_width = 7.5 +trim_height = 9.25 +min_cover_width = bleed + trim_width + spine_width + trim_width + bleed +min_cover_height = bleed + trim_height + bleed + +print "spine width ", spine_width, "inches" +print "min cover width ", min_cover_width, "inches" +print "min cover height ", min_cover_height, "inches" + +print "barcode placeholder width: 2 inches" +print "barcode placeholder height: 1.2 inches" +print "bottom of barcode must be .25 inches from bottom trim line of cover" +print "right side of barcode must be .25 inches to left of spine" diff --git a/docs/designdefense.rst b/docs/designdefense.rst index b92d8810d..8c92298b9 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -3,133 +3,122 @@ Defending Pyramid's Design ========================== -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. - -Pyramid Has Zope Things In It, So It's Too Complex --------------------------------------------------- - -On occasion, someone will feel compelled to post a mailing -list message that reads something like this: - -.. code-block:: text - - had a quick look at pyramid ... too complex to me and not really - understand for which benefits.. I feel should consider whether it's time - for me to step back to django .. I always hated zope (useless ?) - complexity and I love simple way of thinking - -(Paraphrased from a real email, actually.) - -Let's take this criticism point-by point. - -Too Complex -+++++++++++ - -- If you can understand this hello world program, you can use Pyramid: - -.. code-block:: python - :linenos: - - from paste.httpserver import serve - from pyramid.config import Configurator - from pyramid.response import Response - - def hello_world(request): - return Response('Hello world!') - - if __name__ == '__main__': - config = Configurator() - config.add_view(hello_world) - app = config.make_wsgi_app() - serve(app) - -- Pyramid is 5,000 lines of runtime code. Pylons 1.0 has about 3,000 lines - of runtime code. Django has about 60,000 lines of runtime code. You'd - practically need to bend the laws of space and time for Django to be - simpler than Pyramid. - -- It has 600 or more pages of documentation (printed), covering topics from - the very basic to the most advanced. *Nothing* is left undocumented, quite - literally. - -- It has an *awesome*, very helpful community. Visit the #repoze and/or - #pylons IRC channels on freenode.net and see. - -Hate Zope -+++++++++ - -I'm sorry you feel that way. The Zope brand has certainly taken its share of -lumps over the years, and has a reputation for being insular and mysterious. -But the word "Zope" is literally quite meaningless without qualification. -What *part* of Zope do you hate? "Zope" is a brand, not a technology. - -If it's Zope2-the-web-framework, Pyramid is not that. The primary designers -and developers of Pyramid, if anyone, should know. We wrote Pyramid's -predecessor (:mod:`repoze.bfg`), in part, because *we* knew that Zope 2 had -usability issues and limitations. :mod:`repoze.bfg` (and now :app:`Pyramid`) -was written to address these issues. - -If it's Zope3-the-web-framework, Pyramid is *definitely* not that. Making -use of lots of Zope 3 technologies is territory already staked out by the -:term:`Grok` project. Save for the obvious fact that they're both web -frameworks, :mod:`Pyramid` is very, very different than Grok. Grok exposes -lots of Zope technologies to end users. On the other hand, if you need to -understand a Zope-only concept while using Pyramid, then we've failed on some -very basic axis. - -If it's just the word Zope: it's, charitably, only guilt by association. You -need to understand that just because a piece of software internally uses some -package named ``zope.foo``, it doesn't turn the piece of software that uses -it into "Zope". There is a lot of *great* software written that has the word -Zope in its name. Zope is not some sort of monolithic thing, and a lot of -its software is usable externally. - -Zope Is Useless -+++++++++++++++ - -It's not really the job of this document to defend Zope. But Zope has been -around for over 10 years and has an incredibly large, active community. If -you don't believe this, http://taichino.appspot.com/pypi_ranking/authors is -an eye-opening reality check. - -Love Simplicity -+++++++++++++++ +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. + +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 +: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 they were accustomed to building them. No other system save 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 consumble 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. -Years of effort have gone into honing this package and its documentation to -make it as simple as humanly possible for developers to use. Everything is a -tradeoff, of course, and people have their own ideas about what "simple" is. -You may have a style difference if you believe Pyramid is complex. Its -developers obviously disagree. +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` 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 ----------------------------------------------------------- -:app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) -"component registry" as its :term:`application registry` under the -hood. This is a point of some contention. :app:`Pyramid` is of a -:term:`Zope` pedigree, so it was natural for its developers to use a -ZCA registry at its inception. However, we understand that using a -ZCA registry has issues and consequences, which we've attempted to -address as best we can. Here's an introspection about -:app:`Pyramid` use of a ZCA registry, and the trade-offs its usage +:app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) "component +registry" as its :term:`application registry` under the hood. This is a +point of some contention. :app:`Pyramid` is of a :term:`Zope` pedigree, so +it was natural for its developers to use a ZCA registry at its inception. +However, we understand that using a ZCA registry has issues and consequences, +which we've attempted to address as best we can. Here's an introspection +about :app:`Pyramid` use of a ZCA registry, and the trade-offs its usage involves. Problems ++++++++ The "global" API that may be used to access data in a ZCA "component -registry" is not particularly pretty or intuitive, and sometimes it's -just plain obtuse. Likewise, the conceptual load on a casual source -code reader of code that uses the ZCA global API is somewhat high. -Consider a ZCA neophyte reading the code that performs a typical -"unnamed utility" lookup using the :func:`zope.component.getUtility` -global API: +registry" is not particularly pretty or intuitive, and sometimes it's just +plain obtuse. Likewise, the conceptual load on a casual source code reader +of code that uses the ZCA global API is somewhat high. Consider a ZCA +neophyte reading the code that performs a typical "unnamed utility" lookup +using the :func:`zope.component.getUtility` global API: .. ignore-next-block .. code-block:: python @@ -139,74 +128,67 @@ global API: from zope.component import getUtility settings = getUtility(ISettings) -After this code runs, ``settings`` will be a Python dictionary. But -it's unlikely that any "civilian" would know that just by reading the -code. There are a number of comprehension issues with the bit of code -above that are 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 of such code need to understand the concept in order to parse -it. This is problem number one. - -Second, what's this ``ISettings`` thing? It's an :term:`interface`. -Is that important here? Not really, we're just using it as a "key" -for some lookup based on its identity as a marker: it represents an -object that has the 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. 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. - -Fourth, where does ``getUtility`` look to get the data? Well, the -"component 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. +After this code runs, ``settings`` will be a Python dictionary. But it's +unlikely that any "civilian" would know that just by reading the code. There +are a number of comprehension issues with the bit of code above that are +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 +of such code need to understand the concept in order to parse it. This is +problem number one. + +Second, what's this ``ISettings`` thing? It's an :term:`interface`. Is that +important here? Not really, we're just using it as a "key" for some lookup +based on its identity as a marker: it represents an object that has the +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. +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. + +Fourth, where does ``getUtility`` look to get the data? Well, the "component +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. 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, -:term:`ZCML` of course. Sometimes. Or via imperative code. In this -particular case, however, the registration of ``ISettings`` is made by -the framework itself "under the hood": it's not present in any ZCML -nor was it performed imperatively. 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. +"hanging around". But how does the registry get populated? Why, via code +that calls directives like ``config.add_view``. In this particular case, +however, the 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. 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. +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. 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 current request. The application -developer calls it like so: +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 +current request. The application developer calls it like so: .. ignore-next-block .. code-block:: python @@ -234,38 +216,34 @@ is this: return None 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 returns 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 minutiae of the 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 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 -<http://svn.repoze.org/repoze.component/trunk>`_ 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. +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 returns 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 minutiae of the 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 +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 <http://svn.repoze.org/repoze.component/trunk>`_ +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 @@ -276,180 +254,124 @@ ultimately proved a better fit. 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 my 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. - -#) 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 - :linenos: - - from pyramid.interfaces import IAuthenticationPolicy - from zope.component import getUtility - policy = getUtility(IAuthenticationPolicy) - - :app:`Pyramid` code will usually do: - - .. code-block:: python - :linenos: - - from pyramid.interfaces import IAuthenticationPolicy - from pyramid.threadlocal import get_current_registry - 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. - -#) We've turned the component registry used by :app:`Pyramid` into - something that is accessible using the plain old dictionary API - (like the :mod:`repoze.component` API). For example, the snippet - of code in the problem section above was: - - .. code-block:: python - :linenos: +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. - from pyramid.interfaces import ISettings - from zope.component import getUtility - settings = getUtility(ISettings) +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. - In a better world, we might be able to spell this as: +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 - :linenos: - - from pyramid.threadlocal import get_current_registry - - registry = get_current_registry() - settings = registry['settings'] +.. code-block:: python + :linenos: - In this world, we've removed the need to understand utilities and - interfaces, because we've disused them in favor of a plain dictionary - lookup. We *haven't* removed the need to understand the concept of a - *registry*, but for the purposes of this example, it's simply a - dictionary. We haven't killed off the concept of a thread local - either. Let's kill off thread locals, pretending to want to do this - in some code that has access to the :term:`request`: + from pyramid.interfaces import IAuthenticationPolicy + from zope.component import getUtility + policy = getUtility(IAuthenticationPolicy) - .. code-block:: python - :linenos: +:app:`Pyramid` code will usually do: - registry = request.registry - settings = registry['settings'] +.. code-block:: python + :linenos: - In *this* world, we've reduced the conceptual problem to understanding - attributes and the dictionary API. Every Python programmer knows - these things, even framework programmers. + from pyramid.interfaces import IAuthenticationPolicy + from pyramid.threadlocal import get_current_registry + registry = get_current_registry() + policy = registry.getUtility(IAuthenticationPolicy) -While :app:`Pyramid` still uses some suboptimal unnamed utility -registrations, future versions of it will where possible disuse these -things in favor of straight dictionary assignments and lookups, as -demonstrated above, to be kinder to new framework developers. We'll -continue to seek ways to reduce framework developer cognitive load. +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 +++++++++ -Here are the main rationales involved in the :app:`Pyramid` -decision to use the ZCA registry: - -- Pedigree. A nontrivial part of the answer to this question is - "pedigree". Much of the design of :app:`Pyramid` is stolen - directly from :term:`Zope`. Zope uses the ZCA registry to do a - number of tricks. :app:`Pyramid` mimics these tricks, and, - because the ZCA registry works well for that set of tricks, - :app:`Pyramid` uses it for the same purposes. For example, the - way that :app:`Pyramid` maps a :term:`request` to a :term:`view - callable` is lifted almost entirely from Zope. The ZCA registry - plays an important role in the particulars of how this request to - view mapping is done. - -- Features. The ZCA component registry essentially provides what can - be considered something like a "superdictionary", which allows for - more complex lookups than retrieving a value based on a single key. - Some of this lookup capability is very useful for end users, such as - being able to register a 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 +Here are the main rationales involved in the :app:`Pyramid` decision to use +the ZCA registry: + +- Pedigree. A nontrivial part of the answer to this question is "pedigree". + Much of the design of :app:`Pyramid` is stolen directly from :term:`Zope`. + Zope uses the ZCA registry to do a number of tricks. :app:`Pyramid` mimics + these tricks, and, because the ZCA registry works well for that set of + tricks, :app:`Pyramid` uses it for the same purposes. For example, the way + that :app:`Pyramid` maps a :term:`request` to a :term:`view callable` using + :term:`traversal` is lifted almost entirely from Zope. The ZCA registry + plays an important role in the particulars of how this request to view + mapping is done. + +- Features. The ZCA component registry essentially provides what can be + considered something like a "superdictionary", which allows for more + complex lookups than retrieving a value based on a single key. Some of + this lookup capability is very useful for end users, such as being able to + register a 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. -- Composability. A ZCA component registry can be populated - imperatively, or there's an existing mechanism to populate a - registry via the use of a configuration file (ZCML). We didn't need - to write a frontend from scratch to make use of - configuration-file-driven registry population. - -- Pluggability. Use of the ZCA registry allows for framework - extensibility via a well-defined and widely understood plugin - architecture. As long as framework developers and extenders - understand the ZCA registry, it's possible to extend - :app:`Pyramid` almost arbitrarily. For example, it's relatively - easy to build a ZCML directive that registers several views "all at - once", allowing app developers to use that ZCML directive as a - "macro" in code that they write. This is somewhat of a - differentiating feature from other (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. - -- 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 for just these purposes. The ZCA registry - contains optional C code for this purpose which demonstrably has no - (or very few) bugs. - -- Ecosystem. Many existing Zope packages can be used in - :app:`Pyramid` with few (or no) changes due to our use of the ZCA - registry and :term:`ZCML`. +- Composability. A ZCA component registry can be populated imperatively, or + there's an existing mechanism to populate a registry via the use of a + configuration file (ZCML, via :term:`pyramid_zcml`). We didn't need to + write a frontend from scratch to make use of configuration-file-driven + registry population. + +- Pluggability. Use of the ZCA registry allows for framework extensibility + via a well-defined and widely understood plugin architecture. As long as + framework developers and extenders understand the ZCA registry, it's + possible to extend :app:`Pyramid` almost arbitrarily. For example, it's + relatively easy to build a directive that registers several views "all at + once", allowing app developers to use that directive as a "macro" in code + that they write. This is somewhat of a differentiating feature from other + (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. + +- 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 + for just these purposes. The ZCA registry contains optional C code for + this purpose which demonstrably has no (or very few) bugs. + +- Ecosystem. Many existing Zope packages can be used in :app:`Pyramid` with + few (or no) changes due to our use of the ZCA registry. 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 or even know about its presence: 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. - -If you *extend* or *develop* :app:`Pyramid` (create new ZCML directives, use -some of the more obscure "ZCML hooks" as described in :ref:`hooks_chapter`, -or work on the :app:`Pyramid` core code), you will be faced with needing to -understand 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. +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. + +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 +the :app:`Pyramid` core code), you will be faced with needing to understand +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. Pyramid Uses Interfaces Too Liberally ------------------------------------- @@ -458,15 +380,15 @@ In this `TOPP Engineering blog entry <http://www.coactivate.org/projects/topp-engineering/blog/2008/10/20/what-bothers-me-about-the-component-architecture/>`_, Ian Bicking asserts that the way :mod:`repoze.bfg` used a Zope interface to represent an HTTP request method added too much indirection for not enough -gain. We agreed in general, and for this reason, :mod:`repoze.bfg` version 1.1 -(and subsequent versions including :app:`Pyramid` 1.0+) added :term:`view +gain. We agreed in general, and for this reason, :mod:`repoze.bfg` version +1.1 (and subsequent versions including :app:`Pyramid` 1.0+) added :term:`view predicate` and :term:`route predicate` modifiers to view configuration. Predicates are request-specific (or :term:`context` -specific) matching narrowers which don't use interfaces. Instead, each predicate uses a domain-specific string as a match value. -For example, to write a view configuration which matches only requests -with the ``POST`` HTTP request method, you might write a ``@view_config`` +For example, to write a view configuration which matches only requests with +the ``POST`` HTTP request method, you might write a ``@view_config`` decorator which mentioned the ``request_method`` predicate: .. code-block:: python @@ -478,8 +400,7 @@ decorator which mentioned the ``request_method`` predicate: return 'POSTed' You might further narrow the matching scenario by adding an ``accept`` -predicate that narrows matching to something that accepts a JSON -response: +predicate that narrows matching to something that accepts a JSON response: .. code-block:: python :linenos: @@ -490,16 +411,13 @@ response: def post_view(request): return 'POSTed' -Such a view would only match when the request indicated that HTTP -request method was ``POST`` and that the remote user agent passed +Such a view would only match when the request indicated that HTTP request +method was ``POST`` and that the remote user agent passed ``application/json`` (or, for that matter, ``application/*``) in its ``Accept`` request header. "Under the hood", these features make no use of interfaces. -For more information about predicates, see -:ref:`view_predicates_in_1dot1` and :ref:`route_predicates_in_1dot1`. - Many "prebaked" predicates exist. However, use of only "prebaked" predicates, however, doesn't entirely meet Ian's criterion. He would like to be able to match a request using a lambda or another function which interrogates the @@ -528,78 +446,14 @@ Pyramid "Encourages Use of ZCML" -------------------------------- :term:`ZCML` is a configuration language that can be used to configure the -:term:`Zope Component Architecture` registry that :app:`Pyramid` uses as its +:term:`Zope Component Architecture` registry that :app:`Pyramid` uses for application configuration. Often people claim that Pyramid "needs ZCML". -Quick answer: well, it doesn't. At least not anymore. In :mod:`repoze.bfg` -(the predecessor to Pyramid) versions 1.0 and and 1.1, an application needed to -possess a ZCML file for it to begin executing successfully. However, -:mod:`repoze.bfg` 1.2 and greater (including :app:`Pyramid` 1.0) includes a -completely imperative mode for all configuration. You will be able to make -"single file" apps in this mode, which should help people who need to see -everything done completely imperatively. For example, the very most basic -:app:`Pyramid` "helloworld" program has become something like: - -.. code-block:: python - :linenos: - - from webob import Response - from paste.httpserver import serve - from pyramid.config import Configurator - - def hello_world(request): - return Response('Hello world!') - - if __name__ == '__main__': - config = Configurator() - config.add_view(hello_world) - app = config.make_wsgi_app() - serve(app) - -In this mode, no ZCML is required at all. Hopefully this mode will allow -people who are used to doing everything imperatively feel more comfortable. - -Pyramid Uses ZCML; ZCML is XML and I Don't Like XML ---------------------------------------------------- - -:term:`ZCML` is a configuration language in the XML syntax. Due to the -"imperative configuration" feature (new in :mod:`repoze.bfg` 1.2), you don't -need to use ZCML at all. But if you really do want to perform declarative -configuration, perhaps because you want to build an extensible application, you -will need to use and understand it. - -:term:`ZCML` contains elements that are mostly singleton tags that are -called *declarations*. For an example: - -.. code-block:: xml - :linenos: - - <route - view=".views.my_view" - path="/" - name="root" - /> - -This declaration associates a :term:`view` with a route pattern. - -All :app:`Pyramid` declarations are singleton tags, unlike many -other XML configuration systems. No XML *values* in ZCML are -meaningful; it's always just XML tags and attributes. So in the very -common case it's not really very much different than an otherwise -"flat" configuration format like ``.ini``, except a developer can -*create* a directive that requires nesting (none of these exist in -:app:`Pyramid` itself), and multiple "sections" can exist with the -same "name" (e.g. two ``<route>`` declarations) must be able to exist -simultaneously. - -You might think some other configuration file format would be better. -But all configuration formats suck in one way or another. I -personally don't think any of our lives would be markedly better if -the declarative configuration format used by :app:`Pyramid` were -YAML, JSON, or INI. It's all just plumbing that you mostly cut and -paste once you've progressed 30 minutes into your first project. -Folks who tend to agitate for another configuration file format are -folks that haven't yet spent that 30 minutes. +It doesn't. In :app:`Pyramid` 1.0, ZCML doesn't ship as part of the core; +instead it ships in the :term:`pyramid_zcml` add-on package, which is +completely optional. No ZCML is required at all to use :app:`Pyramid`, nor +any other sort of frameworky declarative frontend to application +configuration. .. _model_traversal_confusion: @@ -620,14 +474,18 @@ 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. - -This is understandable. The people who believe it is wrong almost invariably -have all of their data in a relational database. Relational databases aren't +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 +all of their data in a relational database. Relational databases aren't naturally hierarchical, so "traversing" one like a tree is not possible. -Folks who deem traversal unilaterally "wrong" are neglecting to take into -account that many persistence mechanisms *are* hierarchical. Examples +However, folks who deem traversal unilaterally wrong are neglecting to take +into account that many persistence mechanisms *are* hierarchical. Examples include a filesystem, an LDAP database, a :term:`ZODB` (or another type of graph) database, an XML document, and the Python module namespace. It is often convenient to model the frontend to a hierarchical data store as a @@ -645,28 +503,32 @@ resource tree is an excellent way to model this, even if the backend is a relational database. In this situation, the resource tree a just a site structure. -But the point is ultimately moot. 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. +Traversal also offers better composability of applications than URL dispatch, +because it doesn't rely on a fixed ordering of URL matching. You can compose +a set of disparate functionality (and add to it later) around a mapping of +view to resource more predictably than trying to get "the right" ordering of +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 -------------------------------------------------------- -In :app:`Pyramid`, :term:`url dispatch` is the act of resolving a -URL path to a :term:`view` callable by performing pattern matching -against some set of ordered route definitions. The route definitions -are examined in order: the first pattern which matches is used to -associate the URL with a view callable. - -Some people are uncomfortable with this notion, and believe it is -wrong. These are usually people who are steeped deeply in -:term:`Zope`. Zope does not provide any mechanism except -:term:`traversal` to map code to URLs. This is mainly because Zope -effectively requires use of :term:`ZODB`, which is a hierarchical -object store. Zope also supports relational databases, but typically -the code that calls into the database lives somewhere in the ZODB -object graph (or at least is a :term:`view` related to a node in the -object graph), and traversal is required to reach this code. +In :app:`Pyramid`, :term:`url dispatch` is the act of resolving a URL path to +a :term:`view` callable by performing pattern matching against some set of +ordered route definitions. The route definitions are examined in order: the +first pattern which matches is used to associate the URL with a view +callable. + +Some people are uncomfortable with this notion, and believe it is wrong. +These are usually people who are steeped deeply in :term:`Zope`. Zope does +not provide any mechanism except :term:`traversal` to map code to URLs. This +is mainly because Zope effectively requires use of :term:`ZODB`, which is a +hierarchical object store. Zope also supports relational databases, but +typically the code that calls into the database lives somewhere in the ZODB +object graph (or at least is a :term:`view` related to a node in the object +graph), and traversal is required to reach this code. 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 @@ -683,20 +545,19 @@ 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`. +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 ------------------------------------------------------- @@ -728,40 +589,24 @@ arguments to any method of a resource object found via traversal: def aview(self, a, b, c=None): return '%s %s %c' % (a, b, c) -When this method is called as the result of being the published -callable, the Zope request object's GET and POST namespaces are -searched for keys which 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. - -:app:`Pyramid` has neither of these features. :mod:`pyramid` -view callables always accept only ``context`` and ``request`` (or just -``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 obtain information by interrogating the request -object for it in the view body instead of providing magic to do -unpacking into the view argument list. The feature itself also just -seems a bit like a gimmick. Getting the arguments you want explicitly -from the request via getitem is not really very hard; it's certainly -never a bottleneck for the author when he writes web apps. - -It is possible to replicate the Zope-like behavior in a view callable -decorator, however, should you badly want something like it back. No -such decorator currently exists. If you'd like to create one, Google -for "zope mapply" and adapt the function you'll find to a decorator -that pulls the argument mapping information out of the -``request.params`` dictionary. - -A similar feature could be implemented to provide the Django-like -behavior as a decorator by wrapping the view with a decorator that -looks in ``request.matchdict``. - -It's possible at some point that :app:`Pyramid` will grow some form -of argument matching feature (it would be simple to make it an -always-on optional feature that has no cost unless you actually use -it) for, but currently it has none. +When this method is called as the result of being the published callable, the +Zope request object's GET and POST namespaces are searched for keys which +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, :mod:`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. + +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 +out of view callables which are called with arbitrary arguments. See +:ref:`using_a_view_mapper`. Pyramid Provides Too Few "Rails" -------------------------------- @@ -779,32 +624,54 @@ built using :app:`Pyramid` as a base. See also :ref:`apps_are_extensible`. Pyramid Provides Too Many "Rails" --------------------------------- -:app:`Pyramid` provides some features that other web frameworks do -not. Most notably it has machinery which resolves a URL first to a -:term:`context` before calling a view (which has the capability to -accept the context in its argument list), and a declarative -authorization system that makes use of this feature. Most other web -frameworks besides :term:`Zope`, from which the pattern was stolen, -have no equivalent core feature. - -We consider this an important feature for a particular class of -applications (CMS-style applications, which the authors are often -commissioned to write) that usually use :term:`traversal` against a -persistent object graph. The object graph contains security -declarations as :term:`ACL` objects. - -Having context-sensitive declarative security for individual objects -in the object graph is simply required for this class of application. -Other frameworks save for Zope just do not have this feature. This is -one of the primary reasons that :app:`Pyramid` was actually -written. - -If you don't like this, it doesn't mean you can't use -:app:`Pyramid`. Just ignore this feature and avoid configuring an -authorization or authentication policy and using ACLs. You can build -"Pylons-1.X-style" applications using :app:`Pyramid` that use their own -security model via decorators or plain-old-imperative logic in view -code. +:app:`Pyramid` provides some features that other web frameworks do not. +These are features meant for use cases that might not make sense to you if +you're building a simple "bespoke" web application: + +- An optional way to map URLs to code using :term:`traversal` which implies a + walk of a :term:`resource tree`. + +- The ability to aggregate Pyramid application configuration from multiple + 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`). + +- A declarative :term:`authorization` system. + +- Multiple separate I18N :term:`translation string` factories, each of which + can name its own "domain". + +These features are important to the authors of :app:`Pyramid`. The +:app:`Pyramid` authors are often commissioned to build CMS-style +applications. Such applications are often "frameworky" because they have +more than one deployment. Each deployment requires a slightly different +composition of sub-applications, and the framework and sub-applications often +need to be *extensible*. Because the application has more than one +deployment, pluggability and extensibility is important, as maintaining +multiple forks of the application, one per deployment, is extremely +undesirable. Because it's easier to extend a system that uses +:term:`traversal` "from the outside" than it is to do the same in a system +that uses :term:`URL dispatch`, each deployment uses a :term:`resource tree` +composed of a persistent tree of domain model objects, and uses +:term:`traversal` to map :term:`view callable` code to resources in the tree. +The resource tree contains very granular security declarations, as resources +are owned and accessible by different sets of users. Interfaces are used to +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. + +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. Pyramid Is Too Big ------------------ @@ -843,64 +710,59 @@ Pyramid Has Too Many Dependencies --------------------------------- This is true. At the time of this writing, the total number of Python -package distributions that :app:`Pyramid` depends upon transitively -is 18 if you use Python 2.6 or 2.7, or 16 if you use Python 2.4 or -2.5. This is a lot more than zero package distribution dependencies: -a metric which various Python microframeworks and Django boast. - -The :mod:`zope.component` and :mod:`zope.configuration` packages on -which :app:`Pyramid` depends have transitive dependencies on -several other packages (:mod:`zope.schema`, :mod:`zope.i18n`, -:mod:`zope.event`, :mod:`zope.interface`, :mod:`zope.deprecation`, -:mod:`zope.i18nmessageid`). We've been working with the Zope -community to try to collapse and untangle some of these dependencies. -We'd prefer that these packages have fewer packages as transitive -dependencies, and that much of the functionality of these packages was -moved into a smaller *number* of packages. +package distributions that :app:`Pyramid` depends upon transitively is 18 if +you use Python 2.6 or 2.7, or 16 if you use Python 2.4 or 2.5. This is a lot +more than zero package distribution dependencies: a metric which various +Python microframeworks and Django boast. + +The :mod:`zope.component` and :mod:`zope.configuration` packages on which +:app:`Pyramid` depends have transitive dependencies on several other packages +(:mod:`zope.schema`, :mod:`zope.i18n`, :mod:`zope.event`, +:mod:`zope.interface`, :mod:`zope.deprecation`, :mod:`zope.i18nmessageid`). +We've been working with the Zope community to try to collapse and untangle +some of these dependencies. We'd prefer that these packages have fewer +packages as transitive dependencies, and that much of the functionality of +these packages was moved into a smaller *number* of packages. :app:`Pyramid` also has its own direct dependencies, such as :term:`Paste`, :term:`Chameleon`, :term:`Mako` and :term:`WebOb`, and some of these in turn have their own transitive dependencies. -It should be noted that :app:`Pyramid` is positively lithe compared -to :term:`Grok`, a different Zope-based framework. As of this -writing, in its default configuration, Grok has 126 package -distribution dependencies. The number of dependencies required by -:app:`Pyramid` is many times fewer than Grok (or Zope itself, upon -which Grok is based). :app:`Pyramid` has a number of package -distribution dependencies comparable to similarly-targeted frameworks -such as Pylons 1.X. - -We try not to reinvent too many wheels (at least the ones that don't -need reinventing), and this comes at the cost of some number of -dependencies. However, "number of package distributions" is just not -a terribly great metric to measure complexity. For example, the -:mod:`zope.event` distribution on which :app:`Pyramid` depends has -a grand total of four lines of runtime code. As noted above, we're -continually trying to agitate for a collapsing of these sorts of -packages into fewer distribution files. +It should be noted that :app:`Pyramid` is positively lithe compared to +:term:`Grok`, a different Zope-based framework. As of this writing, in its +default configuration, Grok has 109 package distribution dependencies. The +number of dependencies required by :app:`Pyramid` is many times fewer than +Grok (or Zope itself, upon which Grok is based). :app:`Pyramid` has a number +of package distribution dependencies comparable to similarly-targeted +frameworks such as Pylons 1.X. + +We try not to reinvent too many wheels (at least the ones that don't need +reinventing), and this comes at the cost of some number of dependencies. +However, "number of package distributions" is just not a terribly great +metric to measure complexity. For example, the :mod:`zope.event` +distribution on which :app:`Pyramid` depends has a grand total of four lines +of runtime code. As noted above, we're continually trying to agitate for a +collapsing of these sorts of packages into fewer distribution files. Pyramid "Cheats" To Obtain Speed -------------------------------- -Complaints have been lodged by other web framework authors at various -times that :app:`Pyramid` "cheats" to gain performance. One -claimed cheating 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. +Complaints have been lodged by other web framework authors at various times +that :app:`Pyramid` "cheats" to gain performance. One claimed cheating +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 more +readably. As an amelioration, we've commented these sections liberally. Pyramid Gets Its Terminology Wrong ("MVC") ------------------------------------------ -"I'm a MVC web framework user, and I'm confused. :app:`Pyramid` -calls the controller a view! And it doesn't have any controllers." +"I'm a MVC web framework user, and I'm confused. :app:`Pyramid` calls the +controller a view! And it doesn't have any controllers." If you are in this camp, you might have come to expect things about how your existing "MVC" framework uses its terminology. For example, you probably @@ -910,12 +772,11 @@ 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. -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 +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 @@ -946,23 +807,21 @@ the `Model-View-Controller Wikipedia entry 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. - -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. +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. + +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 @@ -980,11 +839,11 @@ the current constraints of the web. 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 chapter named :ref:`extending_chapter`. It is made -possible by the use of the :term:`Zope Component Architecture` and -:term:`ZCML` within :app:`Pyramid`. +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`. "Extensible", in this context, means: @@ -1002,108 +861,99 @@ possible by the use of the :term:`Zope Component Architecture` and ZCA, the original developer does not need to think terribly hard about the mechanics of introducing such a plugpoint. -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 useful 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. - -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, -usually via :term:`ZCML`. 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 needs it 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. - -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 its -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 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 +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 +useful 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. + +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 needs + it 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. + +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 its 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 +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. -While :app:`Pyramid` application 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 -:term:`ZCML` 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 -``configure.zcml`` 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 somewhat -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 -instead use version control branching and merging to manage -application deployment modifications instead, as if you were deploying +While :app:`Pyramid` application 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 +: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 instead 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 @@ -1112,11 +962,11 @@ 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: +: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: #) When I use the security proxy machinery, I can have a view that conditionally displays certain HTML elements (like form fields) or @@ -1132,75 +982,68 @@ access. I like this, because it means: Defense +++++++ -:app:`Pyramid` was developed by folks familiar with Zope 2, which -has a "through the web" security model. This "TTW" security model was -the precursor to Zope 3's security proxies. Over time, as the -:app:`Pyramid` developers (working in Zope 2) created such sites, -we found authorization checks during code interpretation extremely -useful in a minority of projects. But much of the time, TTW -authorization checks usually slowed down the development velocity of -projects that had no delegation requirements. In particular, if we -weren't allowing "untrusted" users to write arbitrary Python code to -be executed by our application, the burden of "through the web" -security checks proved too costly to justify. We (collectively) -haven't written an application on top of which untrusted developers -are allowed to write code in many years, so it seemed to make sense to -drop this model by default in a new 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. - -Justifications for disabling security proxies by default -notwithstanding, given that Zope 3 security proxies are "viral" by -nature, the only requirement to use one is to make sure you wrap a -single object in a security proxy and make sure to access that object -normally when you want proxy security checks to happen. It is -possible to override the :app:`Pyramid` "traverser" for a given -application (see :ref:`changing_the_traverser`). To get Zope3-like -behavior, it is possible to plug in a different traverser which -returns 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. +:app:`Pyramid` was developed by folks familiar with Zope 2, which has a +"through the web" security model. This "TTW" security model was the +precursor to Zope 3's security proxies. Over time, as the :app:`Pyramid` +developers (working in Zope 2) created such sites, we found authorization +checks during code interpretation extremely useful in a minority of projects. +But much of the time, TTW authorization checks usually slowed down the +development velocity of projects that had no delegation requirements. In +particular, if we weren't allowing "untrusted" users to write arbitrary +Python code to be executed by our application, the burden of "through the +web" security checks proved too costly to justify. We (collectively) haven't +written an application on top of which untrusted developers are allowed to +write code in many years, so it seemed to make sense to drop this model by +default in a new 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. + +Justifications for disabling security proxies by default notwithstanding, +given that Zope 3 security proxies are "viral" by nature, the only +requirement to use one is to make sure you wrap a single object in a security +proxy and make sure to access that object normally when you want proxy +security checks to happen. It is possible to override the :app:`Pyramid` +"traverser" for a given application (see :ref:`changing_the_traverser`). To +get Zope3-like behavior, it is possible to plug in a different traverser +which returns 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. .. _microframeworks_smaller_hello_world: 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 -BFG <http://bfg.repoze.org/videos#groundhog1>`_ (the precursor to -Pyramid). 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; in a contest of "whose is shortest", Pyramid indeed loses. - -This loss isn't for lack of trying. Pyramid aims to be 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. +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 BFG <http://bfg.repoze.org/videos#groundhog1>`_ (the +precursor to Pyramid). 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. + +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) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -Please imagine a directory structure with a set of Python files in -it: +Please imagine a directory structure with a set of Python files in it: .. code-block:: text @@ -1248,13 +1091,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 @@ -1263,27 +1106,26 @@ list with two elements to be printed, right? Sadly, no: <function foo at 0x7f4ea41ab230>, <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 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``. - -Let's see what happens when we use the same pattern with the -`Groundhog <http://bfg.repoze.org/videos#groundhog1>`_ microframework. -Replace the contents of ``app.py`` above with this: +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 +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``. + +Let's see what happens when we use the same pattern with the `Groundhog +<https://github.com/Pylons/groundhog>`_ microframework. Replace the contents +of ``app.py`` above with this: .. code-block:: python :linenos: @@ -1317,91 +1159,87 @@ Replace the contents of ``config.py`` above with this: from groundhog import Groundhog gh = Groundhog('myapp', 'seekrit') -How many routes will be registered within the routing table of the -"gh" Groundhog application? If you answered three, you are correct. -How many would a casual reader (and any sane developer) expect to be -registered? If you answered two, you are correct. Will the double -registration be a problem? With our fictional Groundhog framework's -``route`` method backing this application, not really. It will slow -the application down a little bit, because it will need to miss twice -for a route when it does not match. Will it be a problem with another -framework, another application, or another decorator? Who knows. You -need to understand the application in its totality, the framework in -its totality, and the chronology of execution to be able to predict -what the impact of unintentional code double-execution 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. This 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*. Microframework programmers therefore will at some point -then 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. This is a really pretty poor situation to find -yourself in as an application developer: you probably didn't even know -you signed up for the job, because the documentation offered by -decorator-based microframeworks don't warn you about it. - -Python application programmers do not control the module scope codepath. -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 -(mutant) 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. 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 adequnately in the -checkin message which accompanies the fix or documented succinctly enough for -the benefit of the rest of the development team so that the problem never -happens again. It will happen again next month too, 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``. - -Folks who have a large investment in eager decorator-based -configuration that populates an external data structure (such as -microframework authors) may argue that the set of circumstances I -outlined above is anomalous and contrived. They will argue that it -just will never happen. If you never intend your application to grow -beyond one or two or three modules, that's probably true. However, as -your codebase grows, and becomes spread across a greater number of -modules, the circumstances in which module-scope code will be executed -multiple times will become more and more likely to occur and less and -less predictable. It's not responsible to claim that double-execution -of module-scope code will never happen. It will; it's just a matter -of luck, 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 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 code -execution. It's literally impossible for a framework author to know -what will happen in all circumstances ("X is executed, then Y, then X -again.. a train leaves Chicago at 50 mph... "). And 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 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. +How many routes will be registered within the routing table of the "gh" +Groundhog application? If you answered three, you are correct. How many +would a casual reader (and any sane developer) expect to be registered? If +you answered two, you are correct. Will the double registration be a +problem? With our Groundhog framework's ``route`` method backing this +application, not really. It will slow the application down a little bit, +because it will need to miss twice for a route when it does not match. Will +it be a problem with another framework, another application, or another +decorator? Who knows. You need to understand the application in its +totality, the framework in its totality, and the chronology of execution to +be able to predict what the impact of unintentional code double-execution +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. + +Folks who have a large investment in eager decorator-based configuration that +populates an external data structure (such as microframework authors) may +argue that the set of circumstances I outlined above is anomalous and +contrived. They will argue that it just will never happen. If you never +intend your application to grow beyond one or two or three modules, that's +probably true. However, as your codebase grows, and becomes spread across a +greater number of modules, the circumstances in which module-scope code will +be executed multiple times will become more and more likely to occur and less +and less predictable. It's not responsible to claim that double-execution of +module-scope code will never happen. It will; it's just a matter of luck, +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 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 +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 +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. 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: +problem. They allow you to disuse decorator based configuration entirely. +Instead of requiring you to do the following: .. code-block:: python :linenos: @@ -1415,8 +1253,7 @@ entirely. 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: @@ -1431,34 +1268,33 @@ almost-all-imperative: gh.run() This is a generic mode of operation that is encouraged in the Pyramid -documentation. Some existing microframeworks (Flask, in particular) -allow for it as well. None (other than Pyramid) *encourage* it. If -you never expect your application to grow beyond two or three or four -or ten modules, it probably doesn't matter very much which mode you -use. If your application grows large, however, imperative -configuration can provide better predictability. +documentation. Some existing microframeworks (Flask, in particular) allow for +it as well. None (other than Pyramid) *encourage* it. If you never expect +your application to grow beyond two or three or four or ten modules, it +probably doesn't matter very much which mode you use. If your application +grows large, however, imperative configuration can provide better +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 its 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) 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 its attached to, not a shared resource like + a registry in another module. This has the effect that + double-registrations will never be performed. Routes (Usually) Need Relative Ordering +++++++++++++++++++++++++++++++++++++++ Consider the following simple `Groundhog -<http://bfg.repoze.org/videos#groundhog1>`_ application: +<https://github.com/Pylons/groundhog>`_ application: .. code-block:: python :linenos: @@ -1482,8 +1318,8 @@ Consider the following simple `Groundhog app.run() If you run this application and visit the URL ``/admin``, you will see -"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: @@ -1506,54 +1342,50 @@ rearrange the order of the function definitions in the file? 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. - -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 ``/:action`` by virtue of adding routing patterns via -decorators at module 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. - -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. - -You may be willing to maintain an ordering of your view functions -which reifies your routing policy. Your application may be small -enough where this will never cause an issue. If it becomes large -enough to matter, however, I don't envy you. Maintaining that -ordering as your application grows larger will be difficult. At some -point, you will also need to start controlling *import* ordering as -well as function definition ordering. When your application grows -beyond the size of a single file, and when decorators are used to -register views, the non-``__main__`` modules which contain -configuration decorators must be imported somehow for their -configuration to be executed. - -Does that make you a little -uncomfortable? It should, because :ref:`you_dont_own_modulescope`. +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. + +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 +``/:action`` by virtue of adding routing patterns via decorators at module +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. + +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. + +You may be willing to maintain an ordering of your view functions which +reifies your routing policy. Your application may be small enough where this +will never cause an issue. If it becomes large enough to matter, however, I +don't envy you. Maintaining that ordering as your application grows larger +will be difficult. At some point, you will also need to start controlling +*import* ordering as well as function definition ordering. When your +application grows beyond the size of a single file, and when decorators are +used to register views, the non-``__main__`` modules which contain +configuration decorators must be imported somehow for their configuration to +be executed. + +Does that make you a little uncomfortable? It should, because +:ref:`you_dont_own_modulescope`. "Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -In another manifestation of "import fascination", some microframeworks -use the ``import`` statement to get a handle to an object which *is -not logically global*: +In another manifestation of "import fascination", some microframeworks use +the ``import`` statement to get a handle to an object which *is not logically +global*: .. code-block:: python :linenos: @@ -1572,18 +1404,17 @@ not logically global*: # this is executed if the request method was GET or the # credentials were invalid -The `Pylons 1.X <http://pylonshq.com>`_ 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 within an external Python module. However, in -"normal" programs, they are never used to obtain a reference to an -object that has a lifetime measured by the scope of the body of a -function. It would be absurd to try to import, for example, a -variable named ``i`` representing a loop counter defined in the body -of a function. For example, we'd never try to import ``i`` from the +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. + +Import statements in Python (``import foo``, ``from bar import baz``) are +most frequently performed to obtain a reference to an object defined globally +within an external Python module. However, in "normal" programs, they are +never used to obtain a reference to an object that has a lifetime measured by +the scope of the body of a function. It would be absurd to try to import, +for example, a variable named ``i`` representing a loop counter defined in +the body of a function. For example, we'd never try to import ``i`` from the code below: .. code-block:: python @@ -1593,80 +1424,74 @@ 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 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 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. - -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. +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 +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 +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. + +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. Explicitly WSGI +++++++++++++++ -Some microframeworks offer a ``run()`` method of an application object -that executes a default server configuration for easy execution. - -Pyramid doesn't currently try to hide the fact that its router is a -WSGI application behind a convenience ``run()`` API. It just tells -people to 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``, ``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 +Some microframeworks offer a ``run()`` method of an application object that +executes a default server configuration for easy execution. + +Pyramid doesn't currently try to hide the fact that its router is a WSGI +application behind a convenience ``run()`` API. It just tells people to +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``, +``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. -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. +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 +++++++++++ -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 +comments take into account what we've discussed in the :ref:`microframeworks_smaller_hello_world` section. .. code-block:: python @@ -1686,6 +1511,163 @@ where comments take into account what we've discussed in the app = config.make_wsgi_app() # explicitly WSGI serve(app, host='0.0.0.0') # explicitly WSGI +Pyramid Doesn't Offer Pluggable Apps +------------------------------------ + +It is "Pyramidic" to compose multiple external sources into the same +configuration using :ref:`~pyramid.config.Configuration.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 +:ref:`~pyramid.config.Configurator.add_view`, etc). + +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 (such as both externals trying to add a route using the same name, +or both externals trying to add a view with the same set of predicates). +It's awful tempting to call this set of features something that can be +used to compose a system out of "pluggable applications". But in +reality, there are a number of problems with claiming this: + +- The terminology is strained. Pyramid really has no notion of a + plurality of "applications", just a way to compose configuration + from multiple sources to create a single WSGI application. That + WSGI application may gain behavior by including or disincluding + configuration, but once it's all composed together, Pyramid + doesn't really provide any machinery which can be used to demarcate + the boundaries of one "application" (in the sense of configuration + from an external that adds routes, views, etc) from another. + +- Pyramid doesn't provide enough "rails" to make it possible to + integrate truly honest-to-god, download-an-app-from-a-random-place + and-plug-it-in-to-create-a-system "pluggable" applications. + Because Pyramid itself isn't opinionated (it doesn't mandate a + particular kind of database, it offers multiple ways to map URLs + to code, etc), it's unlikely that someone who creates something + "application-like" will be able to casually redistribute it + to J. Random Pyramid User and have it "just work" by asking him + to config.include a function from the package. + This is particularly true of very high level components such + as blogs, wikis, twitter clones, commenting systems, etc. + The "integrator" (the Pyramid developer who has downloaded a + package advertised as a "pluggable app") will almost certainly + have made different choices about e.g. what type of persistence + system he's using, and for the integrator to appease the + requirements of the "pluggable application", he may be required + to set up a different database, make changes to his own code + to prevent his application from "shadowing" the pluggable + app (or vice versa), and any other number of arbitrary + changes. + +For this reason, we claim that Pyramid has "extensible" applications, +not pluggable applications. Any Pyramid application can be extended +without forking it as long as its configuration statements have been +composed into things that can be pulled in via "config.include". + +It's also perfectly reasonable for a single developer or team to create a set +of interoperating components which can be enabled or disabled by using +config.include. That developer or team will be able to provide the "rails" +(by way of making high-level choices about the technology used to create the +project, so there won't be any issues with plugging all of the components +together. The problem only rears its head when the components need to be +distributed to *arbitrary* users. Note that Django has a similar problem +with "pluggable applications" that need to work for arbitrary third parties, +even though they provide many, many more rails than does Pyramid. Even the +rails they provide are not enough to make the "pluggable application" story +really work without local modification. + +Truly pluggable applications need to be created at a much higher level than a +web framework, as no web framework can offer enough constraints to really +make them "work out of the box". They really need to plug into an +application, instead. It would be a noble goal to build an application with +Pyramid that provides these constraints and which truly does offer a way to +plug in applications (Joomla, Plone, Drupal come to mind). + +Pyramid Has Zope Things In It, So It's Too Complex +-------------------------------------------------- + +On occasion, someone will feel compelled to post a mailing list message that +reads something like this: + +.. code-block:: text + + had a quick look at pyramid ... too complex to me and not really + understand for which benefits.. I feel should consider whether it's time + for me to step back to django .. I always hated zope (useless ?) + complexity and I love simple way of thinking + +(Paraphrased from a real email, actually.) + +Let's take this criticism point-by point. + +Too Complex ++++++++++++ + +If you can understand this hello world program, you can use Pyramid: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + if __name__ == '__main__': + config = Configurator() + config.add_view(hello_world) + app = config.make_wsgi_app() + serve(app) + +Pyramid has ~ 650 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 +#repoze and/or #pylons IRC channels on freenode.net and see. + +Hate Zope ++++++++++ + +I'm sorry you feel that way. The Zope brand has certainly taken its share of +lumps over the years, and has a reputation for being insular and mysterious. +But the word "Zope" is literally quite meaningless without qualification. +What *part* of Zope do you hate? "Zope" is a brand, not a technology. + +If it's Zope2-the-web-framework, Pyramid is not that. The primary designers +and developers of Pyramid, if anyone, should know. We wrote Pyramid's +predecessor (:mod:`repoze.bfg`), in part, because *we* knew that Zope 2 had +usability issues and limitations. :mod:`repoze.bfg` (and now :app:`Pyramid`) +was written to address these issues. + +If it's Zope3-the-web-framework, Pyramid is *definitely* not that. Making +use of lots of Zope 3 technologies is territory already staked out by the +:term:`Grok` project. Save for the obvious fact that they're both web +frameworks, :mod:`Pyramid` is very, very different than Grok. Grok exposes +lots of Zope technologies to end users. On the other hand, if you need to +understand a Zope-only concept while using Pyramid, then we've failed on some +very basic axis. + +If it's just the word Zope: this can only be guilt by association. Because a +piece of software internally uses some package named ``zope.foo``, it doesn't +turn the piece of software that uses it into "Zope". There is a lot of +*great* software written that has the word Zope in its name. Zope is not +some sort of monolithic thing, and a lot of its software is usable +externally. And while it's not really the job of this document to defend it, +Zope has been around for over 10 years and has an incredibly large, active +community. If you don't believe this, +http://taichino.appspot.com/pypi_ranking/authors is an eye-opening reality +check. + +Love Simplicity ++++++++++++++++ + +Years of effort have gone into honing this package and its documentation to +make it as simple as humanly possible for developers to use. Everything is a +tradeoff, of course, and people have their own ideas about what "simple" is. +You may have a style difference if you believe Pyramid is complex. Its +developers obviously disagree. + Other Challenges ---------------- diff --git a/docs/foreword.rst b/docs/foreword.rst index 9ae7eda60..aa8d7c77b 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -8,70 +8,74 @@ Foreword Creations*, which later became *Zope Corporation*. He has been a widely respected member of the Python community since 1994. -Fortunate. +Some times amazing things can actually happen. -As I reflect upon the BFG web framework and this book by Chris to -document it, I keep coming back to the same word. Certainly the -conventional wisdom is clear: "Don't we have too many web frameworks, -paired with outdated books?" Yes we do, but to the contrary and for -that very reason, we are fortunate to have this book and this -framework. +In the world of web frameworks, the rate of radioactive decay is very high. +Projects are starting, splintering, folding, and clashing constantly. For +Python, there are over 50 listed web frameworks. In some ways this shows +health and experimentation. Yet others have started to ask: "Is this really +good for Python developers?" -Chris McDonough first came to work with us at Digital Creations almost -a decade ago, just after there existed a Zope. We were all pioneers: -the first open source application server, one of the first open source -web companies to get serious investment, and entrants in nearly every -book and article about the open source space. Zope wasn't just a -unique business model, though. It really was, as quoted at the time, -one of the places where open source delivered fresh ideas in design -and architecture. +This book is the result of an event which bucked this trend, an event which +Armin Ronacher wrote was "one of the greatest moves in Python's web framework +history." Two projects merged and are bringing in a third. Consolidation +won a victory over splintering. -Then a decade happened. Bubbles burst and the new new thing became -the old new thing, many times in succession. All of us changed jobs, -worked on a variety of endeavors, and big dreams yielded to small -realities. Somehow, though the trajectory was unforeseen, we have -orbited back to the same spot. Older, wiser, but with similar ideas -and familiar faces. Back to dream again. +As someone from the Zope world, I had a strong interest in repoze.bfg. I +viewed it as the escape hatch for Zope, teleporting us into the modern world +of Python development, permitting but not requiring Zope-style idioms. Chris +McDonough established a great brand for repoze.bfg: small, documented, +tested, fast, stable, friendly. As the project manager for a very large +repoze.bfg application, I can strongly attest that it was a home run on those +points. -We are fortunate to have BFG. It really does carve out a unique spot -in the Python web frameworks landscape. It permits the core good -ideas from Zope, while not requiring them. Moreover, the reason -you'll love it has less to do with Zope and more to do with the old -fashioned stuff: +But in a crowded web frameworks landscape, repoze.bfg was a long-shot to get +critical mass. It had a lot to offer, but was missing critical pieces such +as momentum and name recognition. -- A superb commitment to outstanding and constantly updated documentation +Pylons has long been viewed as holding the number two spot in Python web +frameworks. It is one of (if not the) first "modern" web framework. With +lots of users, and a "full-stack" framework atop it (TurboGears), Pylons had +momentum and name recognition aplenty. But it needed more resources to +accomplish its goals of an architectural transition, and Ben Bangert needed +to share the load as architect during the transition. -- An equal commitment to quality: test coverage, performance, and - documented compatibility +Ben and Chris started talking during 2011 about architectural patterns and +discovered Pylons and repoze.bfg covered almost exactly the same surface +area. After some experiments, it became clear that, technically at least, +the next version of Pylons could be the same as the next version of +repoze.bfg. -- Adult supervision and minimalism in the design, with "pay only for - what you eat" simplicity +But what about the non-technical parts? It was one thing to consolidate +code. Consolidating projects was new territory. -For those of us from the Zope world, BFG permits our still-unique -ideas while teleporting us into the modern world of Python web -programming. It is fascinating, liberating, and rejuvenating. We are -able to cast off old sins and legitimately reclaim the title of best -damn game in town. Quite a coup: whether you considered Zope but -turned away, or became an adopter, you'll find BFG the new new new -thing. +I was fortunate to meet with the principals in Las Vegas and watch as they +hashed out the idea. The projects would merge and keep the Pylons identity. +repoze.bfg would sacrifice its identity, but provide the technical +foundation. All the resources from the two projects would be combined. -We are also fortunate to have this book. We never had such a resource -in Zope, even though we funded the writing of the first book a decade -ago. In retrospect, the answer is obvious: a second group tried to -retrofit a book onto code created by the first group. The true magic -in BFG is that the top-notch documentation is written by the same -person as the top-notch code, a person with equal passion and -commitment to both. Rarely are we so fortunate. +I'll confess, I had high hopes for the outcome. Now that the merge has +happened and 1.0 released, I can honestly say it has done better than I could +have imagined. The story of "consolidation" is catching on, and interest in +working together is growing. Pyramid 1.0 is very, very high quality and +ready to go for PyCon 2011. People interested in "simple, fast, documented, +tested" have a strong framework and healthy project. -Which brings us to the final point. We are fortunate to have Chris. -I personally consider myself lucky to have worked with him and to be -his friend this past decade. He has changed my thinking in numerous -ways, fundamentally improving the way I view many things. He's the -best person I know in the world of open source, and I get to be in -business with him. Fortunate indeed. +It took humility, patience, and pragmatism to reach this point of obvious +success. Certainly by the project leaders, who each had to give up some of +their sovereignty and sacred cows. But as well, each community had to +discuss the challenges, the various alternatives for going forward, and the +pros and cons of consolidation in general but also this particular +consolidation. That such a conversation and change could happen in a +responsible, adult fashion speaks volumes about the strength and maturity of +each community. -I very much hope you enjoy this book and get involved with BFG. We -use it for applications as small as "hello world" demos up to -scalable, re-usable, half-a-million-dollar projects. May you find -BFG, and the book, to be a high-quality, honest, and durable framework -choice for your work as well. +What might happen in 2011? TurboGears is considering a move into the +umbrella Pylons Project. As Armin writes in his post, there is fertile +ground for consolidation at other layers. In my own interests, I hope the +worlds of Zope and Plone view Pyramid as the base for the next decade of +their ideas. But also, the Pylons Project as a vibrant home for such ideas. + +Congratulations, Pylons Project. Not only have you accelerated your spot on +the Python web frameworks chart, but you have injected the word +"consolidation" into the lexicon of hot ideas for 2011. diff --git a/docs/glossary.rst b/docs/glossary.rst index a3aacebce..0aef7672b 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -24,12 +24,13 @@ Glossary information about response objects. Repoze - "Repoze" is essentially a "brand" of software developed by - `Agendaless Consulting <http://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 <http://svn.repoze.org>`_. + "Repoze" is essentially a "brand" of software developed by `Agendaless + Consulting <http://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 + <http://svn.repoze.org>`_. Pyramid was originally known as + :mod:`repoze.bfg`. setuptools `Setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_ @@ -54,7 +55,8 @@ Glossary For example, the asset specification ``my.package:static/baz.css`` identifies the file named ``baz.css`` in the ``static`` subdirectory of the ``my.package`` - Python :term:`package`. + Python :term:`package`. See :ref:`asset_specifications` for more + info. package A directory on disk which contains an ``__init__.py`` file, making @@ -88,9 +90,9 @@ Glossary dotted Python name A reference to a Python object by name using a string, in the form ``path.to.modulename:attributename``. Often used in Paste and - setuptools configurations. A variant is used in dotted names - within :term:`ZCML` attributes that name objects (such as the ZCML - "view" directive's "view" attribute): the colon (``:``) is not + setuptools configurations. A variant is used in dotted names within + configurator method arguments that name objects (such as the "add_view" + method's "view" and "context" attributes): the colon (``:``) is not used; in its place is a dot. view @@ -111,14 +113,14 @@ Glossary about :app:`Pyramid` view callables. view configuration - View configuration is the act of associating a :term:`view - callable` with configuration information. This configuration - information helps map a given :term:`request` to a particular view - callable and it can influence the response of a view callable. - :app:`Pyramid` views can be configured via :term:`imperative - configuration`, :term:`ZCML` or by a special ``@view_config`` - decorator coupled with a :term:`scan`. See :ref:`views_chapter` - for more information about view configuration. + View configuration is the act of associating a :term:`view callable` + with configuration information. This configuration information helps + map a given :term:`request` to a particular view callable and it can + influence the response of a view callable. :app:`Pyramid` views can be + configured via :term:`imperative configuration`, or by a special + ``@view_config`` decorator coupled with a :term:`scan`. See + :ref:`view_config_chapter` for more information about view + configuration. view name The "URL name" of a view, e.g ``index.html``. If a view is @@ -180,10 +182,10 @@ Glossary :ref:`urldispatch_chapter` for more information. context - An resource in the resource tree that is found during :term:`traversal` + A resource in the resource tree that is found during :term:`traversal` or :term:`URL dispatch` based on URL data; if it's found via traversal, it's usually a :term:`resource` object that is part of a resource tree; - if it's found via :term:`URL dispatch`, it's a object manufactured on + if it's found via :term:`URL dispatch`, it's an object manufactured on behalf of the route's "factory". A context resource becomes the subject of a :term:`view`, and often has security information attached to it. See the :ref:`traversal_chapter` chapter and the @@ -284,7 +286,7 @@ Glossary WSGI `Web Server Gateway Interface <http://wsgi.org/>`_. This is a Python standard for connecting web applications to web servers, - similar to the concept of Java Servlets. ``pyramid`` requires + similar to the concept of Java Servlets. :app:`Pyramid` requires that your application be served as a WSGI application. middleware @@ -310,7 +312,8 @@ Glossary `A full-featured Python web framework <http://djangoproject.com>`_. Pylons - `A lightweight Python web framework <http://pylonshq.com>`_. + `A lightweight Python web framework <http://pylonshq.com>`_ and a + predecessor of Pyramid. ZODB `Zope Object Database <http://zodb.org>`_, a @@ -376,29 +379,10 @@ Glossary ultimately a :term:`view`). See also :term:`url dispatch`. route configuration - Route configuration is the act of using :term:`imperative - configuration` or a :term:`ZCML` ``<route>`` statement to - associate request parameters with a particular :term:`route` using - pattern matching and :term:`route predicate` statements. See - :ref:`urldispatch_chapter` for more information about route - configuration. - - ZCML - `Zope Configuration Markup Language - <http://www.muthukadan.net/docs/zca.html#zcml>`_, an XML dialect - used by Zope and :app:`Pyramid` for configuration tasks. ZCML - is capable of performing different types of :term:`configuration - declaration`, but its primary purpose in :app:`Pyramid` is to - perform :term:`view configuration` and :term:`route configuration` - within the ``configure.zcml`` file in a :app:`Pyramid` - application. You can use ZCML as an alternative to - :term:`imperative configuration`. - - ZCML directive - A ZCML "tag" such as ``<view>`` or ``<route>``. - - ZCML declaration - The concrete use of a :term:`ZCML directive` within a ZCML file. + Route configuration is the act of associating request parameters with a + particular :term:`route` using pattern matching and :term:`route + predicate` statements. See :ref:`urldispatch_chapter` for more + information about route configuration. Zope Component Architecture The `Zope Component Architecture @@ -456,9 +440,9 @@ Glossary subscriber A callable which receives an :term:`event`. A callable becomes a - subscriber via :term:`imperative configuration` or the - ``<subscriber>`` ZCML directive. See :ref:`events_chapter` for - more information. + subscriber via :term:`imperative configuration` or via + :term:`configuration decoration`. See :ref:`events_chapter` for more + information. request type An attribute of a :term:`request` that allows for specialization @@ -477,8 +461,7 @@ Glossary An indexing and search facility (fielded and full-text) based on `zope.index <http://pypi.python.org/pypi/zope.index>`_. See `the documentation <http://docs.repoze.org/catalog>`_ for more - information. A tutorial for its usage in :app:`Pyramid` - exists in :ref:`catalog_tutorial`. + information. repoze.who `Authentication middleware <http://docs.repoze.org/who>`_ for @@ -505,7 +488,7 @@ Glossary available as its ``__parent__`` attribute. root factory - The "root factory" of an :app:`Pyramid` application is called + The "root factory" of a :app:`Pyramid` application is called on every request sent to the application. The root factory returns the traversal root of an application. It is conventionally named ``get_root``. An application may supply a @@ -516,7 +499,7 @@ Glossary all URL-to-view code mappings. SQLAlchemy - `SQLAlchemy' <http://www.sqlalchemy.org/>`_ is an object + `SQLAlchemy <http://www.sqlalchemy.org/>`_ is an object relational mapper used in tutorials within this documentation. JSON @@ -576,15 +559,13 @@ Glossary also `PEP 318 <http://www.python.org/dev/peps/pep-0318/>`_. configuration declaration - An individual method call made to an instance of a - :app:`Pyramid` :term:`Configurator` object which performs an - arbitrary action, such as registering a :term:`view configuration` - (via the ``view`` method of the configurator) or :term:`route - configuration` (via the ``route`` method of the configurator). A - set of configuration declarations is also usually implied via the - use of a :term:`ZCML declaration` within an application, or a set - of configuration declarations might be performed by a :term:`scan` - of code in a package. + An individual method call made to an instance of a :app:`Pyramid` + :term:`Configurator` object which performs an arbitrary action, such as + registering a :term:`view configuration` (via the ``add_view`` method of + the configurator) or :term:`route configuration` (via the ``add_route`` + method of the configurator). A set of configuration declarations is + also implied by the :term:`configuration decoration` detected by a + :term:`scan` of code in a package. configuration decoration Metadata implying one or more :term:`configuration declaration` @@ -608,8 +589,8 @@ Glossary declaration` required by your application. declarative configuration - The configuration mode in which you use :term:`ZCML` to make - a set of :term:`configuration declaration` statements. + The configuration mode in which you use :term:`ZCML` to make a set of + :term:`configuration declaration` statements. See :term:`pyramid_zcml`. Not Found view An :term:`exception view` invoked by :app:`Pyramid` when the @@ -638,16 +619,15 @@ Glossary information. 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 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`. See also - the `threading.local documentation - <http://docs.python.org/library/threading.html#threading.local>` - for more information. + 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 + 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`. See also the `threading.local + documentation + <http://docs.python.org/library/threading.html#threading.local>`_ for + more information. multidict An ordered dictionary that can have multiple values for each @@ -664,11 +644,11 @@ Glossary and Chris McDonough. See also http://agendaless.com . Jython - A `Python implementation <http://www.jython.org/>` written for + 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 <http://python.org>`_ in which :app:`Pyramid` is written. CPython @@ -831,9 +811,10 @@ Glossary View handler A view handler ties together :meth:`pyramid.config.Configurator.add_route` and - :meth:`pyramid.config.Configurator.add_view` to make it more - convenient to register a collection of views as a single class when - using :term:`url dispatch`. See also :ref:`handlers_chapter`. + :meth:`pyramid.config.Configurator.add_view` to make it more convenient + to register a collection of views as a single class when using + :term:`url dispatch`. View handlers ship as part of the + :term:`pyramid_handlers` add-on package. Deployment settings Deployment settings are settings passed to the :term:`Configurator` as a @@ -845,4 +826,68 @@ Glossary `WebTest <http://pythonpaste.org/webtest/>`_ is a package which can help you write functional tests for your WSGI application. + WebError + WSGI middleware which can display debuggable traceback information in + the browser when an exception is raised by a Pyramid application. See + http://pypi.python.org/pypi/WebError . + + view mapper + A view mapper is a class which implements the + :class:`pyramid.interfaces.IViewMapperFactory` interface, which performs + view argument and return value mapping. This is a plug point for + extension builders, not normally used by "civilians". + + matchdict + The dictionary attached to the :term:`request` object as + ``request.matchdict`` when a :term:`URL dispatch` route has been matched. + Its keys are names as identified within the route pattern; its values are + the values matched by each pattern name. + + pyramid_zcml + An add-on package to :app:`Pyramid` which allows applications to be + configured via ZCML. It is available on :term:`PyPI`. If you use + ``pyramid_zcml``, you can use ZCML as an alternative to + :term:`imperative configuration`. + + ZCML + `Zope Configuration Markup Language + <http://www.muthukadan.net/docs/zca.html#zcml>`_, an XML dialect + used by Zope and :term:`pyramid_zcml` for configuration tasks. + + ZCML directive + A ZCML "tag" such as ``<view>`` or ``<route>``. + + ZCML declaration + The concrete use of a :term:`ZCML directive` within a ZCML file. + + 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/ . + + pyramid_jinja2 + :term:`Jinja2` templating system bindings for Pyramid, documented at + http://docs.pylonsproject.org/projects/pyramid_jinja2/dev/ . This + package also includes a paster template named + ``pyramid_jinja2_starter``, which creates an application package based + on the Jinja2 templating system. + + pyramid_sqla + A package which provides a Pylons-esque paster template which sports + support for :term:`view handler` application development, + :term:`SQLAlchemy` support, and other Pylons-like features. See + https://bytebucket.org/sluggo/pyramid_sqla/wiki/html/index.html for more + information. + + Pyramid Cookbook + An additional documentation resource for Pyramid which presents topical, + practical usages of Pyramid available via + http://docs.pylonsproject.org/ . + + distutils + The standard system for packaging and distributing Python packages. See + http://docs.python.org/distutils/index.html for more information. + :term:`setuptools` is actually an *extension* of the Distutils. + + diff --git a/docs/index.rst b/docs/index.rst index 343fb28ba..5815d3b32 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,14 +6,9 @@ The Pyramid Web Application Development Framework :app:`Pyramid` is a small, fast, down-to-earth Python web application development framework. It is developed as part of the `Pylons Project -<http://docs.pylonshq.com/>`_. It is licensed under a `BSD-like license +<http://docs.pylonsproject.org/>`_. It is licensed under a `BSD-like license <http://repoze.org/license.html>`_. -.. note:: - - Pyramid is the latest iteration of the web framework previously known as - :mod:`repoze.bfg`. - Front Matter ============ @@ -23,6 +18,14 @@ Front Matter copyright.rst conventions.rst +"What's New" Documents +====================== + +.. toctree:: + :maxdepth: 2 + + whatsnew-1.0 + Narrative documentation ======================= @@ -38,18 +41,17 @@ Narrative documentation in chapter form explaining how to use narr/firstapp narr/project narr/startup - narr/resourcelocation narr/urldispatch + narr/muchadoabouttraversal narr/traversal narr/views narr/renderers narr/templates + narr/viewconfig narr/resources - narr/static + narr/assets narr/webob narr/sessions - narr/flash - narr/csrf narr/security narr/hybrid narr/i18n @@ -58,9 +60,8 @@ Narrative documentation in chapter form explaining how to use narr/environment narr/testing narr/hooks - narr/declarative + narr/advconfig narr/extending - narr/assets narr/router narr/threadlocals narr/zca @@ -75,26 +76,21 @@ applications to various platforms. .. toctree:: :maxdepth: 2 - tutorials/wiki/index.rst tutorials/wiki2/index.rst + tutorials/wiki/index.rst tutorials/bfg/index.rst - tutorials/cmf/index.rst tutorials/gae/index.rst tutorials/modwsgi/index.rst - tutorials/zeo/index.rst - tutorials/catalog/index.rst Reference Material ================== -Reference material includes API documentation and documentation of -every :app:`Pyramid` :term:`ZCML directive`. +Reference material includes documentation for every :app:`Pyramid` API. .. toctree:: :maxdepth: 2 api - zcml Detailed Change History ======================= @@ -136,11 +132,10 @@ earlier version of this application runs the `repoze.org git clone git://github.com/Pylons/virginia.git `shootout <https://github.com/Pylons/shootout>`_ is an example "idea -competition" application by Carlos de la Guardia. It demonstrates a hybrid -of :term:`URL dispatch` and :term:`traversal` and integration with -`SQLAlchemy <http://www.sqlalchemy.org/>`_, :term:`repoze.who`, and -`Deliverance <http://www.deliveranceproject.org/>`_. Check this application -out of version control via: +competition" application by Carlos de la Guardia and Lukasz Fidosz. It +demonstrates :term:`URL dispatch`, simple authentication, integration +with `SQLAlchemy <http://www.sqlalchemy.org/>`_ and ``pyramid_simpleform``. +Check this application out of version control via: .. code-block:: text @@ -176,7 +171,7 @@ commenting, and file uploads. See the `KARL site Support and Development ======================= -The `Pylons Project web site <http://docs.pylonshq.com/>`_ is the main online +The `Pylons Project web site <http://pylonsproject.org/>`_ is the main online source of :app:`Pyramid` support and development information. To report bugs, use the `issue tracker @@ -197,7 +192,7 @@ To check out the trunk via ``git``, use this command: To find out how to become a contributor to :app:`Pyramid`, please see the `contributor's section of the documentation -<http://docs.pylonshq.com/index.html#contributing>`_. +<http://docs.pylonsproject.org/index.html#contributing>`_. Index and Glossary ================== diff --git a/docs/latexindex.rst b/docs/latexindex.rst index 713c9841f..a4926bf30 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -31,18 +31,17 @@ Narrative Documentation narr/configuration narr/firstapp narr/project - narr/resourcelocation narr/urldispatch + narr/muchadoabouttraversal narr/traversal narr/views narr/renderers narr/templates + narr/viewconfig narr/resources - narr/static + narr/assets narr/webob narr/sessions - narr/flash - narr/csrf narr/security narr/hybrid narr/i18n @@ -51,10 +50,8 @@ Narrative Documentation narr/environment narr/testing narr/hooks - narr/declarative + narr/advconfig narr/extending - narr/assets - narr/router narr/startup narr/threadlocals narr/zca @@ -67,13 +64,11 @@ Tutorials .. toctree:: :maxdepth: 1 - tutorials/bfg/index.rst tutorials/wiki/index.rst tutorials/wiki2/index.rst + tutorials/bfg/index.rst tutorials/gae/index.rst tutorials/modwsgi/index.rst - tutorials/zeo/index.rst - tutorials/catalog/index.rst .. _api_reference: @@ -99,7 +94,6 @@ API Reference api/renderers api/request api/response - api/router api/scripting api/security api/settings @@ -110,33 +104,6 @@ API Reference api/view api/wsgi -.. _zcml_reference: - -ZCML Directive Reference -@@@@@@@@@@@@@@@@@@@@@@@@ - -.. toctree:: - :maxdepth: 1 - - zcml/aclauthorizationpolicy - zcml/adapter - zcml/authtktauthenticationpolicy - zcml/asset - zcml/configure - zcml/default_permission - zcml/forbidden - zcml/include - zcml/notfound - zcml/remoteuserauthenticationpolicy - zcml/renderer - zcml/repozewho1authenticationpolicy - zcml/route - zcml/scan - zcml/static - zcml/subscriber - zcml/utility - zcml/view - .. backmatter:: Glossary and Index diff --git a/docs/make_book b/docs/make_book new file mode 100755 index 000000000..dc8381845 --- /dev/null +++ b/docs/make_book @@ -0,0 +1,4 @@ +#!/bin/sh +make clean latex SPHINXBUILD=../bookenv/bin/sphinx-build BOOK=1 +cd _build/latex && make all-pdf + diff --git a/docs/narr/MyProject/MANIFEST.in b/docs/narr/MyProject/MANIFEST.in new file mode 100644 index 000000000..fa1692163 --- /dev/null +++ b/docs/narr/MyProject/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include myproject *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml diff --git a/docs/narr/MyProject/development.ini b/docs/narr/MyProject/development.ini index 48fc81b8c..29486ce56 100644 --- a/docs/narr/MyProject/development.ini +++ b/docs/narr/MyProject/development.ini @@ -20,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root +keys = root, myproject [handlers] keys = console @@ -32,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_myproject] +level = DEBUG +handlers = +qualname = myproject + [handler_console] class = StreamHandler args = (sys.stderr,) @@ -39,6 +44,6 @@ 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] %(message)s # End logging configuration diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/narr/MyProject/myproject/templates/mytemplate.pt index 02fc00eeb..632c34876 100644 --- a/docs/narr/MyProject/myproject/templates/mytemplate.pt +++ b/docs/narr/MyProject/myproject/templates/mytemplate.pt @@ -1,76 +1,106 @@ -<!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"> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xml:lang="en" + xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.application_url}/static/favicon.ico" /> - <link rel="stylesheet" href="${request.application_url}/static/pylons.css" type="text/css" media="screen" charset="utf-8" /> - <link href="http://fonts.googleapis.com/css?family=Neuton&subset=latin" rel="stylesheet" type="text/css" media="screen" charset="utf-8" /> - <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Nobile:regular,italic,bold,bolditalic&subset=latin" type="text/css" media="screen" charset="utf-8" /> - <!--[if lte IE 6]> - <link rel="stylesheet" href="${request.application_url}/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> - <![endif]--> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('myproject:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('myproject:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" + href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('myproject: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.application_url}/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> - </div> - </div> - <div id="middle"> - <div class="middle align-center"> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonshq.com/pyramid/dev/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://pylonshq.com">Pylons Website</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> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div> + <img src="${request.static_url('myproject:static/pyramid.png')}" + width="750" height="169" alt="pyramid"/> + </div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, + an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" + action="http://docs.pylonsproject.org/pyramid/dev/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/dev/#narrative-documentation"> + Narrative Documentation + </a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation"> + API Documentation + </a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials"> + Tutorials + </a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history"> + Change History + </a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications"> + Sample Applications + </a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development"> + Support and Development + </a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid"> + IRC Channel + </a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2010, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/narr/MyProject/myproject/tests.py b/docs/narr/MyProject/myproject/tests.py index b14fb37af..5fa710278 100644 --- a/docs/narr/MyProject/myproject/tests.py +++ b/docs/narr/MyProject/myproject/tests.py @@ -1,15 +1,13 @@ import unittest -from pyramid.config import Configurator from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() def test_my_view(self): from myproject.views import my_view diff --git a/docs/narr/MyProject/production.ini b/docs/narr/MyProject/production.ini new file mode 100644 index 000000000..c1d0eee82 --- /dev/null +++ b/docs/narr/MyProject/production.ini @@ -0,0 +1,63 @@ +[app:MyProject] +use = egg:MyProject +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[pipeline:main] +pipeline = + weberror + MyProject + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, myproject + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_myproject] +level = INFO +handlers = +qualname = myproject + +[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/narr/advconfig.rst b/docs/narr/advconfig.rst new file mode 100644 index 000000000..099bce35f --- /dev/null +++ b/docs/narr/advconfig.rst @@ -0,0 +1,474 @@ +.. index:: + pair: advanced; configuration + +.. _advconfig_narr: + +Advanced Configuration +====================== + +To support application extensibility, the :app:`Pyramid` +:term:`Configurator`, by default, detects configuration conflicts and allows +you to include configuration imperatively from other packages or modules. It +also, by default, performs configuration in two separate phases. This allows +you to ignore relative configuration statement ordering in some +circumstances. + +.. index:: + single: imperative configuration + +.. _conflict_detection: + +Conflict Detection +------------------ + +Here's a familiar example of one of the simplest :app:`Pyramid` applications, +configured imperatively: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + if __name__ == '__main__': + config = Configurator() + config.add_view(hello_world) + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +When you start this application, all will be OK. However, what happens if we +try to add another view to the configuration with the same set of +:term:`predicate` arguments as one we've already added? + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + def goodbye_world(request): + return Response('Goodbye world!') + + if __name__ == '__main__': + config = Configurator() + + config.add_view(hello_world, name='hello') + + # conflicting view configuration + config.add_view(goodbye_world, name='hello') + + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +The application now has two conflicting view configuration statements. When +we try to start it again, it won't start. Instead, we'll receive a traceback +that ends something like this: + +.. code-block:: guess + :linenos: + + Traceback (most recent call last): + File "app.py", line 12, in <module> + app = config.make_wsgi_app() + File "pyramid/config.py", line 839, in make_wsgi_app + self.commit() + File "pyramid/pyramid/config.py", line 473, in commit + self._ctx.execute_actions() + File "zope/configuration/config.py", line 600, in execute_actions + for action in resolveConflicts(self.actions): + File "zope/configuration/config.py", line 1507, in resolveConflicts + raise ConfigurationConflictError(conflicts) + zope.configuration.config.ConfigurationConflictError: + Conflicting configuration actions + For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>, + None, None, None, None, None, False, None, None, None) + ('app.py', 14, '<module>', 'config.add_view(hello_world)') + ('app.py', 17, '<module>', 'config.add_view(hello_world)') + +This traceback is trying to tell us: + +- We've got conflicting information for a set of view configuration + statements (The ``For:`` line). + +- There are two statements which conflict, shown beneath the ``For:`` line: + ``config.add_view(hello_world. 'hello')`` on line 14 of ``app.py``, and + ``config.add_view(goodbye_world, 'hello')`` on line 17 of ``app.py``. + +These two configuration statements are in conflict because we've tried to +tell the system that the set of :term:`predicate` values for both view +configurations are exactly the same. Both the ``hello_world`` and +``goodbye_world`` views are configured to respond under the same set of +circumstances. This circumstance: the :term:`view name` (represented by the +``name=`` predicate) is ``hello``. + +This presents an ambiguity that :app:`Pyramid` cannot resolve. Rather than +allowing the circumstance to go unreported, by default Pyramid raises a +:exc:`ConfigurationConflictError` error and prevents the application from +running. + +Conflict detection happens for any kind of configuration: imperative +configuration or configuration that results from the execution of a +:term:`scan`. + +Manually Resolving Conflicts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are a number of ways to manually resolve conflicts: the "right" way, by +strategically using :meth:`pyramid.config.Configurator.commit`, or by using +an "autocommitting" configurator. + +The Right Thing ++++++++++++++++ + +The most correct way to resolve conflicts is to "do the needful": change your +configuration code to not have conflicting configuration statements. The +details of how this is done depends entirely on the configuration statements +made by your application. Use the detail provided in the +:exc:`ConfigurationConflictError` to track down the offending conflicts and +modify your configuration code accordingly. + +If you're getting a conflict while trying to extend an existing application, +and that application has a function which performs configuration like this +one: + +.. code-block:: python + :linenos: + + def add_routes(config): + config.add_route(...) + +Don't call this function directly with ``config`` as an argument. Instead, +use :meth:`pyramid.config.Configuration.include`: + +.. code-block:: python + :linenos: + + config.include(add_routes) + +Using :meth:`~pyramid.config.Configuration.include` instead of calling the +function directly provides a modicum of automated conflict resolution, with +the configuration statements you define in the calling code overriding those +of the included function. See also :ref:`automatic_conflict_resolution` and +:ref:`including_configuration`. + +Using ``config.commit()`` ++++++++++++++++++++++++++ + +You can manually commit a configuration by using the +:meth:`~pyramid.config.Configurator.commit` method between configuration +calls. For example, we prevent conflicts from occurring in the application +we examined previously as the result of adding a ``commit``. Here's the +application that generates conflicts: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + def goodbye_world(request): + return Response('Goodbye world!') + + if __name__ == '__main__': + config = Configurator() + + config.add_view(hello_world, name='hello') + + # conflicting view configuration + config.add_view(goodbye_world, name='hello') + + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +We can prevent the two ``add_view`` calls from conflicting by issuing a call +to :meth:`~pyramid.config.Configurator.commit` between them: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.config import Configurator + from pyramid.response import Response + + def hello_world(request): + return Response('Hello world!') + + def goodbye_world(request): + return Response('Goodbye world!') + + if __name__ == '__main__': + config = Configurator() + + config.add_view(hello_world, name='hello') + + config.commit() # commit any pending configuration actions + + # no-longer-conflicting view configuration + config.add_view(goodbye_world, name='hello') + + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +In the above example we've issued a call to +:meth:`~pyramid.config.Configurator.commit` between the two ``add_view`` +calls. :meth:`~pyramid.config.Configurator.commit` will cause any pending +configuration statements. + +Calling :meth:`~pyramid.config.Configurator.commit` is safe at any time. It +executes all pending configuration actions and leaves the configuration +action list "clean". + +Note that :meth:`~pyramid.config.Configurator.commit` has no effect when +you're using an *autocommitting* configurator (see +:ref:`autocommitting_configurator`). + +.. _autocommitting_configurator: + +Using An Autocommitting Configurator +++++++++++++++++++++++++++++++++++++ + +You can also use a heavy hammer to circumvent conflict detection by using a +configurator constructor parameter: ``autocommit=True``. For example: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + if __name__ == '__main__': + config = Configurator(autocommit=True) + +When the ``autocommit`` parameter passed to the Configurator is ``True``, +conflict detection (and :ref:`twophase_config`) is disabled. Configuration +statements will be executed immediately, and succeeding statements will +override preceding ones. + +:meth:`~pyramid.config.Configurator.commit` has no effect when ``autocommit`` +is ``True``. + +If you use a Configurator in code that performs unit testing, it's usually a +good idea to use an autocommitting Configurator, because you are usually +unconcerned about conflict detection or two-phase configuration in test code. + +.. _automatic_conflict_resolution: + +Automatic Conflict Resolution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your code uses the :meth:`~pyramid.config.Configurator.include` method to +include external configuration, some conflicts are automatically resolved. +Configuration statements that are made as the result of an "include" will be +overridden by configuration statements that happen within the caller of +the "include" method. + +Automatic conflict resolution supports this goal: if a user wants to reuse a +Pyramid application, and they want to customize the configuration of this +application without hacking its code "from outside", they can "include" a +configuration function from the package and override only some of its +configuration statements within the code that does the include. No conflicts +will be generated by configuration statements within the code which does the +including, even if configuration statements in the included code would +conflict if it was moved "up" to the calling code. + +Methods Which Provide Conflict Detection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These are the methods of the configurator which provide conflict detection: + +:meth:`~pyramid.config.Configurator.add_view`, +:meth:`~pyramid.config.Configurator.add_route`, +:meth:`~pyramid.config.Configurator.add_renderer`, +:meth:`~pyramid.config.Configurator.set_request_factory`, +:meth:`~pyramid.config.Configurator.set_renderer_globals_factory` +:meth:`~pyramid.config.Configurator.set_locale_negotiator` and +:meth:`~pyramid.config.Configurator.set_default_permission`. + +Some other methods of the configurator also indirectly provide conflict +detection, because they're implemented in terms of conflict-aware methods: + +- :meth:`~pyramid.config.Configurator.add_route` does a second type of + conflict detection when a ``view`` parameter is passed (it calls + ``add_view``). + +- :meth:`~pyramid.config.Configurator.static_view`, a frontend for + ``add_route`` and ``add_view``. + +.. _including_configuration: + +Including Configuration from External Sources +--------------------------------------------- + +Some application programmers will factor their configuration code in such a +way that it is easy to reuse and override configuration statements. For +example, such a developer might factor out a function used to add routes to +his application: + +.. code-block:: python + :linenos: + + def add_routes(config): + config.add_route(...) + +Rather than calling this function directly with ``config`` as an argument. +Instead, use :meth:`pyramid.config.Configuration.include`: + +.. code-block:: python + :linenos: + + config.include(add_routes) + +Using ``include`` rather than calling the function directly will allow +:ref:`automatic_conflict_resolution` to work. + +:meth:`~pyramid.config.Configuration.include` can also accept a :term:`module` +as an argument: + +.. code-block:: python + :linenos: + + import myapp + + config.include(myapp) + +For this to work properly, the ``myapp`` module must contain a callable with +the special name ``includeme``, which should perform configuration (like the +``add_routes`` callable we showed above as an example). + +:meth:`~pyramid.config.Configuration.include` can also accept a :term:`dotted +Python name` to a function or a module. + +.. note: See :ref:`the_include_tag` for a declarative alternative to + the :meth:`~pyramid.config.Configurator.include` method. + +.. _twophase_config: + +Two-Phase Configuration +----------------------- + +When a non-autocommitting :term:`Configurator` is used to do configuration +(the default), configuration execution happens in two phases. In the first +phase, "eager" configuration actions (actions that must happen before all +others, such as registering a renderer) are executed, and *discriminators* +are computed for each of the actions that depend on the result of the eager +actions. In the second phase, the discriminators of all actions are compared +to do conflict detection. + +Due to this, for configuration methods that have no internal ordering +constraints, execution order of configuration method calls is not important. +For example, the relative ordering of +:meth:`~pyramid.config.Configurator.add_view` and +:meth:`~pyramid.config.Configurator.add_renderer` is unimportant when a +non-autocommitting configurator is used. This code snippet: + +.. code-block:: python + :linenos: + + config.add_view('some.view', renderer='path_to_custom/renderer.rn') + config.add_renderer('.rn', SomeCustomRendererFactory) + +Has the same result as: + +.. code-block:: python + :linenos: + + config.add_renderer('.rn', SomeCustomRendererFactory) + config.add_view('some.view', renderer='path_to_custom/renderer.rn') + +Even though the view statement depends on the registration of a custom +renderer, due to two-phase configuration, the order in which the +configuration statements are issued is not important. ``add_view`` will be +able to find the ``.rn`` renderer even if ``add_renderer`` is called after +``add_view``. + +The same is untrue when you use an *autocommitting* configurator (see +:ref:`autocommitting_configurator`). When an autocommitting configurator is +used, two-phase configuration is disabled, and configuration statements must +be ordered in dependency order. + +Some configuration methods, such as +:meth:`~pyramid.config.Configurator.add_route` have internal ordering +constraints: the routes they imply require relative ordering. Such ordering +constraints are not absolved by two-phase configuration. Routes are still +added in configuration execution order. + +.. _add_directive: + +Adding Methods to the Configurator via ``add_directive`` +-------------------------------------------------------- + +Framework extension writers can add arbitrary methods to a +:term:`Configurator` by using the +:meth:`pyramid.config.Configurator.add_directive` method of the configurator. +This makes it possible to extend a Pyramid configurator in arbitrary ways, +and allows it to perform application-specific tasks more succinctly. + +The :meth:`~pyramid.config.Configurator.add_directive` method accepts two +positional arguments: a method name and a callable object. The callable +object is usually a function that takes the configurator instance as its +first argument and accepts other arbitrary positional and keyword arguments. +For example: + +.. code-block:: python + :linenos: + + from pyramid.events import NewRequest + from pyramid.config import Configurator + + def add_newrequest_subscriber(config, subscriber): + config.add_subscriber(subscriber, NewRequest). + + if __name__ == '__main__': + config = Configurator() + config.add_directive('add_newrequest_subscriber', + add_newrequest_subscriber) + +Once :meth:`~pyramid.config.Configurator.add_directive` is called, a user can +then call the method by its given name as if it were a built-in method of the +Configurator: + +.. code-block:: python + :linenos: + + def mysubscriber(event): + print event.request + + config.add_newrequest_subscriber(mysubscriber) + +A call to :meth:`~pyramid.config.Configurator.add_directive` is often +"hidden" within an ``includeme`` function within a "frameworky" package meant +to be included as per :ref:`including_configuration` via +:meth:`~pyramid.config.Configurator.include`. For example, if you put this +code in a package named ``pyramid_subscriberhelpers``: + +.. code-block:: python + :linenos: + + def includeme(config) + config.add_directive('add_newrequest_subscriber', + add_newrequest_subscriber) + +The user of the add-on package ``pyramid_subscriberhelpers`` would then be +able to install it and subsequently do: + +.. code-block:: python + :linenos: + + def mysubscriber(event): + print event.request + + from pyramid.config import Configurator + config = Configurator() + config.include('pyramid_subscriberhelpers') + config.add_newrequest_subscriber(mysubscriber) diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 1932e19ff..74fa7723e 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -1,74 +1,396 @@ .. index:: single: assets + single: static asssets .. _assets_chapter: -Assets -====== +Static Assets +============= An :term:`asset` is any file contained within a Python :term:`package` which is *not* a Python source code file. For example, each of the following is an asset: -- a :term:`Chameleon` template file contained within a Python package. +- a GIF image file contained within a Python package or contained within any + subdirectory of a Python package. -- a GIF image file contained within a Python package. +- a CSS file contained within a Python package or contained within any + subdirectory of a Python package. -- a CSS file contained within a Python package. - -- a JavaScript source file contained within a Python package. +- a JavaScript source file contained within a Python package or contained + within any subdirectory of a Python package. - A directory within a package that does not have an ``__init__.py`` in it (if it possessed an ``__init__.py`` it would *be* a package). +- a :term:`Chameleon` or :term:`Mako` template file contained within a Python + package. + The use of assets is quite common in most web development projects. For example, when you create a :app:`Pyramid` application using one of the available "paster" templates, as described in :ref:`creating_a_project`, the directory representing the application contains a Python :term:`package`. Within that Python package, there are directories full of files which are -assets. For example, there is a ``templates`` directory which contains -``.pt`` files, and a ``static`` directory which contains ``.css``, ``.js``, -and ``.gif`` files. +static assets. For example, there's a ``static`` directory which contains +``.css``, ``.js``, and ``.gif`` files. These asset files are delivered when +a user visits an application URL. -.. _understanding_assets: +.. _asset_specifications: -Understanding Assets --------------------- +Understanding Asset Specifications +---------------------------------- Let's imagine you've created a :app:`Pyramid` application that uses a :term:`Chameleon` ZPT template via the -:func:`pyramid.chameleon_zpt.render_template_to_response` API. For example, -the application might address the asset named ``templates/some_template.pt`` -using that API within a ``views.py`` file inside a ``myapp`` package: +:func:`pyramid.renderers.render_to_response` API. For example, the +application might address the asset using the :term:`asset specification` +``myapp:templates/some_template.pt`` using that API within a ``views.py`` +file inside a ``myapp`` package: .. ignore-next-block .. code-block:: python :linenos: - from pyramid.chameleon_zpt import render_template_to_response - render_template_to_response('templates/some_template.pt') + from pyramid.renderers import render_to_response + render_to_response('myapp:templates/some_template.pt', {}, request) -"Under the hood", when this API is called, :app:`Pyramid` attempts -to make sense out of the string ``templates/some_template.pt`` -provided by the developer. To do so, it first finds the "current" -package. The "current" package is the Python package in which the -``views.py`` module which contains this code lives. This would be the -``myapp`` package, according to our example so far. By resolving the -current package, :app:`Pyramid` has enough information to locate -the actual template file. These are the elements it needs: +"Under the hood", when this API is called, :app:`Pyramid` attempts to make +sense out of the string ``myapp:templates/some_template.pt`` provided by the +developer. This string is an :term:`asset specification`. It is composed of +two parts: - The *package name* (``myapp``) -- The *asset name* (``templates/some_template.pt``) +- The *asset name* (``templates/some_template.pt``), relative to the package + directory. + +The two parts are separated by the colon character. + +:app:`Pyramid` uses the Python :term:`pkg_resources` API to resolve the +package name and asset name to an absolute (operating-system-specific) file +name. It eventually passes this resolved absolute filesystem path to the +Chameleon templating engine, which then uses it to load, parse, and execute +the template file. + +There is a second form of asset specification: a *relative* asset +specification. Instead of using an "absolute" asset specification which +includes the package name, in certain circumstances you can omit the package +name from the specification. For example, you might be able to use +``templates/mytemplate.pt`` instead of ``myapp:templates/some_template.pt``. +Such asset specifications are usually relative to a "current package." The +"current package" is usually the package which contains the code that *uses* +the asset specification. :app:`Pyramid` APIs which accept relative asset +specifications typically describe what the asset is relative to in their +individual documentation. + +.. index:: + single: add_static_view + +.. _static_assets_section: + +Serving Static Assets +--------------------- + +:app:`Pyramid` makes it possible to serve up static asset files from a +directory on a filesystem to an application user's browser. Use the +:meth:`pyramid.config.Configurator.add_static_view` to instruct +:app:`Pyramid` to serve static assets such as JavaScript and CSS files. This +mechanism makes a directory of static files available at a name relative to +the application root URL, e.g. ``/static`` or as an external URL. + +.. note:: :meth:`~pyramid.config.Configurator.add_static_view` cannot serve a + single file, nor can it serve a directory of static files directly + relative to the root URL of a :app:`Pyramid` application. For these + features, see :ref:`advanced_static`. + +Here's an example of a use of +:meth:`~pyramid.config.Configurator.add_static_view` that will serve files up +from the ``/var/www/static`` directory of the computer which runs the +:app:`Pyramid` application as URLs beneath the ``/static`` URL prefix. + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + config.add_static_view(name='static', path='/var/www/static') + +The ``name`` prepresents a URL *prefix*. In order for files that live in the +``path`` directory to be served, a URL that requests one of them must begin +with that prefix. In the example above, ``name`` is ``static``, and ``path`` +is ``/var/www/static``. In English, this means that you wish to serve the +files that live in ``/var/www/static`` as sub-URLs of the ``/static`` URL +prefix. Therefore, the file ``/var/www/static/foo.css`` will be returned +when the user visits your application's URL ``/static/foo.css``. + +A static directory named at ``path`` may contain subdirectories recursively, +and any subdirectories may hold files; these will be resolved by the static +view as you would expect. The ``Content-Type`` header returned by the static +view for each particular type of file is dependent upon its file extension. + +By default, all files made available via +:meth:`~pyramid.config.Configurator.add_static_view` are accessible by +completely anonymous users. Simple authorization can be required, however. +To protect a set of static files using a permission, in addition to passing +the required ``name`` and ``path`` arguments, also pass the ``permission`` +keyword argument to :meth:`~pyramid.config.Configurator.add_static_view`. +The value of the ``permission`` argument represents the :term:`permission` +that the user must have relative to the current :term:`context` when the +static view is invoked. A user will be required to possess this permission +to view any of the files represented by ``path`` of the static view. If your +static assets must be protected by a more complex authorization scheme, +see :ref:`advanced_static`. + +Here's another example that uses an :term:`asset specification` instead of an +absolute path as the ``path`` argument. To convince +:meth:`~pyramid.config.Configurator.add_static_view` to serve files up under +the ``/static`` URL from the ``a/b/c/static`` directory of the Python package +named ``some_package``, we can use a fully qualified :term:`asset +specification` as the ``path``: + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + config.add_static_view(name='static', path='some_package:a/b/c/static') + +The ``path`` provided to :meth:`~pyramid.config.Configurator.add_static_view` +may be a fully qualified :term:`asset specification` or an *absolute path*. + +Instead of representing a URL prefix, the ``name`` argument of a call to +:meth:`~pyramid.config.Configurator.add_static_view` can alternately be a +*URL*. Each of examples we've seen so far have shown usage of the ``name`` +argument as a URL prefix. However, when ``name`` is a *URL*, static assets +can be served from an external webserver. In this mode, the ``name`` is used +as the URL prefix when generating a URL using :func:`pyramid.url.static_url`. + +For example, :meth:`~pyramid.config.Configurator.add_static_view` may +be fed a ``name`` argument which is ``http://example.com/images``: + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + config.add_static_view(name='http://example.com/images', + path='mypackage:images') + +Because :meth:`~pyramid.config.Configurator.add_static_view` is provided with +a ``name`` argument that is the URL ``http://example.com/images``, subsequent +calls to :func:`~pyramid.url.static_url` with paths that start with the +``path`` argument passed to +:meth:`~pyramid.config.Configurator.add_static_view` will generate a URL +something like ``http://example.com/images/logo.png``. The external +webserver listening on ``example.com`` must be itself configured to respond +properly to such a request. The :func:`~pyramid.url.static_url` API is +discussed in more detail later in this chapter. + +.. index:: + single: generating static asset urls + single: static asset urls + +.. _generating_static_asset_urls: + +Generating Static Asset URLs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When a :meth:`~pyramid.config.Configurator.add_static_view` method is used to +register a static asset directory, a special helper API named +:func:`pyramid.url.static_url` can be used to generate the appropriate URL +for an asset that lives in one of the directories named by the static +registration ``path`` attribute. + +For example, let's assume you create a set of static declarations like so: + +.. code-block:: python + :linenos: + + config.add_static_view(name='static1', path='mypackage:assets/1') + config.add_static_view(name='static2', path='mypackage:assets/2') + +These declarations create URL-accessible directories which have URLs that +begin with ``/static1`` and ``/static2``, respectively. The assets in the +``assets/1`` directory of the ``mypackage`` package are consulted when a user +visits a URL which begins with ``/static1``, and the assets in the +``assets/2`` directory of the ``mypackage`` package are consulted when a user +visits a URL which begins with ``/static2``. + +You needn't generate the URLs to static assets "by hand" in such a +configuration. Instead, use the :func:`~pyramid.url.static_url` API to +generate them for you. For example: + +.. code-block:: python + :linenos: + + from pyramid.url import static_url + from pyramid.chameleon_zpt import render_template_to_response + + def my_view(request): + css_url = static_url('mypackage:assets/1/foo.css', request) + js_url = static_url('mypackage:assets/2/foo.js', request) + return render_template_to_response('templates/my_template.pt', + css_url = css_url, + js_url = js_url) + +If the request "application URL" of the running system is +``http://example.com``, the ``css_url`` generated above would be: +``http://example.com/static1/foo.css``. The ``js_url`` generated +above would be ``http://example.com/static2/foo.js``. + +One benefit of using the :func:`~pyramid.url.static_url` function rather than +constructing static URLs "by hand" is that if you need to change the ``name`` +of a static URL declaration, the generated URLs will continue to resolve +properly after the rename. + +URLs may also be generated by :func:`~pyramid.url.static_url` to static assets +that live *outside* the :app:`Pyramid` application. This will happen when +the :meth:`~pyramid.config.Configurator.add_static_view` API associated with +the path fed to :func:`~pyramid.url.static_url` is a *URL* instead of a view +name. For example, the ``name`` argument may be ``http://example.com`` while +the the ``path`` given may be ``mypackage:images``: + +.. code-block:: python + :linenos: + + config.add_static_view(name='http://example.com/images', + path='mypackage:images') + +Under such a configuration, the URL generated by ``static_url`` for +assets which begin with ``mypackage:images`` will be prefixed with +``http://example.com/images``: + +.. code-block:: python + :linenos: + + static_url('mypackage:images/logo.png', request) + # -> http://example.com/images/logo.png + +Using :func:`~pyramid.url.static_url` in conjunction with a +:meth:`~pyramid.configuration.Configurator.add_static_view` makes it possible +to put static media on a separate webserver during production (if the +``name`` argument to :meth:`~pyramid.config.Configurator.add_static_view` is a +URL), while keeping static media package-internal and served by the +development webserver during development (if the ``name`` argument to +:meth:`~pyramid.config.Configurator.add_static_view` is a URL prefix). To +create such a circumstance, we suggest using the +:attr:`pyramid.registry.Registry.settings` API in conjunction with a setting +in the application ``.ini`` file named ``media_location``. Then set the +value of ``media_location`` to either a prefix or a URL depending on whether +the application is being run in development or in production (use a different +``.ini`` file for production than you do for development). This is just a +suggestion for a pattern; any setting name other than ``media_location`` +could be used. + +.. index:: + single: static assets view + +.. _advanced_static: + +Advanced: Serving Static Assets Using a View Callable +----------------------------------------------------- + +For more flexibility, static assets can be served by a :term:`view callable` +which you register manually. For example, if you're using :term:`URL +dispatch`, you may want static assets to only be available as a fallback if +no previous route matches. Alternately, you might like to serve a particular +static asset manually, because its download requires authentication. + +Note that you cannot use the :func:`~pyramid.url.static_url` API to generate +URLs against assets made accessible by registering a custom static view. + +Root-Relative Custom Static View (URL Dispatch Only) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :class:`pyramid.view.static` helper class generates a Pyramid view +callable. This view callable can serve static assets from a directory. An +instance of this class is actually used by the +:meth:`~pyramid.config.Configurator.add_static_view` configuration method, so +its behavior is almost exactly the same once it's configured. + +.. warning:: The following example *will not work* for applications that use + :term:`traversal`, it will only work if you use :term:`URL dispatch` + exclusively. The root-relative route we'll be registering will always be + matched before traversal takes place, subverting any views registered via + ``add_view`` (at least those without a ``route_name``). A + :class:`~pyramid.view.static` static view cannot be made root-relative when + you use traversal. + +To serve files within a directory located on your filesystem at +``/path/to/static/dir`` as the result of a "catchall" route hanging from the +root that exists at the end of your routing table, create an instance of the +:class:`~pyramid.view.static` class inside a ``static.py`` file in your +application root as below. + +.. ignore-next-block +.. code-block:: python + :linenos: + + from pyramid.view import static + static_view = static('/path/to/static/dir') + +.. note:: For better cross-system flexibility, use an :term:`asset + specification` as the argument to :class:`~pyramid.view.static` instead of + a physical absolute filesystem path, e.g. ``mypackage:static`` instead of + ``/path/to/mypackage/static``. + +Subsequently, you may wire the files that are served by this view up to be +accessible as ``/<filename>`` using a configuration method in your +application's startup code. + +.. code-block:: python + :linenos: + + # .. every other add_route declaration should come + # before this one, as it will, by default, catch all requests + + config.add_route('catchall_static', '/*subpath', 'myapp.static.static_view') + +The special name ``*subpath`` above is used by the +:class:`~pyramid.view.static` view callable to signify the path of the file +relative to the directory you're serving. + +Registering A View Callable to Serve a "Static" Asset +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can register a simple view callable to serve a single static asset. To +do so, do things "by hand". First define the view callable. + +.. code-block:: python + :linenos: + + import os + from webob import Response + + def favicon_view(request): + here = os.path.dirname(__file__) + icon = open(os.path.join(here, 'static', 'favicon.ico')) + return Response(content_type='image/x-icon', app_iter=icon) + +The above bit of code within ``favicon_view`` computes "here", which is a +path relative to the Python file in which the function is defined. It then +uses the Python ``open`` function to obtain a file handle to a file within +"here" named ``static``, and returns a response using the open the file +handle as the response's ``app_iter``. It makes sure to set the right +content_type too. + +You might register such a view via configuration as a view callable that +should be called as the result of a traversal: + +.. code-block:: python + :linenos: + + config.add_view('myapp.views.favicon_view', name='favicon.ico') + +Or you might register it to be the view callable for a particular route: + +.. code-block:: python + :linenos: + + config.add_route('favicon', '/favicon.ico', + view='myapp.views.favicon_view') -:app:`Pyramid` uses the :term:`pkg_resources` API to resolve the package name -and asset name to an absolute (operating-system-specific) file name. It -eventually passes this resolved absolute filesystem path to the Chameleon -templating engine, which then uses it to load, parse, and execute the -template file. +Because this is a simple view callable, it can be protected with a +:term:`permission` or can be configured to respond under different +circumstances using :term:`view predicate` arguments. -Package names often contain dots. For example, ``pyramid`` is a package. -Asset names usually look a lot like relative UNIX file paths. .. index:: pair: overriding; assets @@ -111,7 +433,7 @@ feature, a :term:`Configurator` API exists named setuptools :term:`pkg_resources` API. .. note:: The :term:`ZCML` directive named ``asset`` serves the same purpose - as the :meth:`pyramid.config.Configurator.override_asset` method. + as the :meth:`~pyramid.config.Configurator.override_asset` method. .. index:: single: override_asset @@ -121,7 +443,7 @@ feature, a :term:`Configurator` API exists named The ``override_asset`` API ~~~~~~~~~~~~~~~~~~~~~~~~~~ -An individual call to :meth:`pyramid.config.Configurator.override_asset` +An individual call to :meth:`~pyramid.config.Configurator.override_asset` can override a single asset. For example: .. ignore-next-block @@ -174,7 +496,7 @@ if you want to override assets for both ``some.package:templates``, and The package name in a specification may start with a dot, meaning that the package is relative to the package in which the configuration construction file resides (or the ``package`` argument to the -:class:`pyramid.config.Configurator` class construction). +:class:`~pyramid.config.Configurator` class construction). For example: .. ignore-next-block diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 394603946..6360dc574 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -127,7 +127,7 @@ The scanning machinery imports each module and subpackage in a package or module recursively, looking for special attributes attached to objects defined within a module. These special attributes are typically attached to code via the use of a :term:`decorator`. For example, the -:class:`pyramid.view.view_config` decorator can be attached to a function or +:class:`~pyramid.view.view_config` decorator can be attached to a function or instance method. Once scanning is invoked, and :term:`configuration decoration` is found by @@ -136,7 +136,7 @@ behalf: these calls replace the need to add imperative configuration statements that don't live near the code being configured. In the example above, the scanner translates the arguments to -:class:`pyramid.view.view_config` into a call to the +:class:`~pyramid.view.view_config` into a call to the :meth:`pyramid.config.Configurator.add_view` method, effectively: .. ignore-next-block @@ -151,9 +151,7 @@ Declarative Configuration A third mode of configuration can be employed when you create a :app:`Pyramid` application named *declarative configuration*. This mode uses an XML language known as :term:`ZCML` to represent configuration statements -rather than Python. ZCML is often used when application extensibility is -important. Most of the examples in the narrative portion of this -documentation concentrate on imperative configuration rather than ZCML, but -almost everything that can be configured imperatively can also be configured -via ZCML. See :ref:`declarative_chapter` for more information about ZCML. +rather than Python. ZCML is not built-in to Pyramid, but almost everything +that can be configured imperatively can also be configured via ZCML if you +install the :term:`pyramid_zcml` package. diff --git a/docs/narr/csrf.rst b/docs/narr/csrf.rst deleted file mode 100644 index 7586b0ed7..000000000 --- a/docs/narr/csrf.rst +++ /dev/null @@ -1,63 +0,0 @@ -.. _csrf_chapter: - -Preventing Cross-Site Request Forgery Attacks -============================================= - -`Cross-site request forgery -<http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a -phenomenon whereby a user with an identity on your website might click on a -URL or button on another website which unwittingly redirects the user to your -application to perform some command that requires elevated privileges. - -You can avoid most of these attacks by making sure that a the correct *CSRF -token* has been set in an :app:`Pyramid` session object before performing any -actions in code which requires elevated privileges and is invoked via a form -post. To use CSRF token support, you must enable a :term:`session factory` -as described in :ref:`using_the_default_session_factory` or -:ref:`using_alternate_session_factories`. - -Using the ``session.new_csrf_token`` Method -------------------------------------------- - -To add a CSRF token to the session, use the ``session.new_csrf_token`` method. - -.. code-block:: python - :linenos: - - token = request.session.new_csrf_token() - -The ``.new_csrf_token`` method accepts no arguments. It returns a *token* -string, which will be opaque and randomized. This token will also be set -into the session, awaiting pickup by the ``session.get_csrf_token`` method. -You can subsequently use the returned token as the value of a hidden field in -a form that posts to a method that requires elevated privileges. The handler -for the form post should use ``session.get_csrf_token`` (explained below) to -obtain the current CSRF token related to the user from the session, and -compare it to the value of the hidden form field. - -Using the ``session.get_csrf_token`` Method -------------------------------------------- - -To get the current CSRF token from the session, use the -``session.get_csrf_token`` method. - -.. code-block:: python - :linenos: - - token = request.session.get_csrf_token() - -The ``get_csrf_token`` method accepts no arguments. It returns the "current" -*token* string (as per the last call to ``session.new_csrf_token``). You can -then use it to compare against the token provided within form post hidden -value data. For example, if your form rendering included the CSRF token -obtained via ``session.new_csrf_token`` as a hidden input field named -``csrf_token``: - -.. code-block:: python - :linenos: - - token = request.session.get_csrf_token() - if token != request.POST['csrf_token']: - raise ValueError('CSRF token did not match') - - diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst deleted file mode 100644 index deccb6c48..000000000 --- a/docs/narr/declarative.rst +++ /dev/null @@ -1,1274 +0,0 @@ -.. _declarative_chapter: - -Declarative Configuration -========================= - -The mode of configuration most comprehensively detailed by examples in -narrative chapters in this book is "imperative" configuration. This is the -configuration mode in which a developer cedes the least amount of control to -the framework; it's "imperative" because you express the configuration -directly in Python code, and you have the full power of Python at your -disposal as you issue configuration statements. However, another mode of -configuration exists within :app:`Pyramid`, which often provides better -extensibility and configuration conflict detection. - -A complete listing of ZCML directives is available within -:ref:`zcml_directives`. This chapter provides an overview of how you might -get started with ZCML and highlights some common tasks performed when you use -ZCML. You can get a better understanding of when it's appropriate to use -ZCML from :ref:`extending_chapter`. - -.. index:: - single: declarative configuration - -.. _declarative_configuration: - -Declarative Configuration -------------------------- - -A :app:`Pyramid` application can be configured "declaratively", if so -desired. Declarative configuration relies on *declarations* made external to -the code in a configuration file format named :term:`ZCML` (Zope -Configuration Markup Language), an XML dialect. - -A :app:`Pyramid` application configured declaratively requires not -one, but two files: a Python file and a :term:`ZCML` file. - -In a file named ``helloworld.py``: - -.. code-block:: python - :linenos: - - from paste.httpserver import serve - from pyramid.response import Response - from pyramid.config import Configurator - - def hello_world(request): - return Response('Hello world!') - - if __name__ == '__main__': - config = Configurator() - config.begin() - config.load_zcml('configure.zcml') - config.end() - app = config.make_wsgi_app() - serve(app, host='0.0.0.0') - -In a file named ``configure.zcml`` in the same directory as the -previously created ``helloworld.py``: - -.. code-block:: xml - :linenos: - - <configure xmlns="http://pylonshq.com/pyramid"> - - <include package="pyramid.includes" /> - - <view - view="helloworld.hello_world" - /> - - </configure> - -This pair of files forms an application functionally equivalent to the -application we created earlier in :ref:`imperative_configuration`. -Let's examine the differences between that code listing and the code -above. - -In :ref:`imperative_configuration`, we had the following lines within -the ``if __name__ == '__main__'`` section of ``helloworld.py``: - -.. code-block:: python - :linenos: - - if __name__ == '__main__': - config = Configurator() - config.begin() - config.add_view(hello_world) - config.end() - app = config.make_wsgi_app() - serve(app, host='0.0.0.0') - -In our "declarative" code, we've removed the call to ``add_view`` and -replaced it with a call to the -:meth:`pyramid.config.Configurator.load_zcml` method so that -it now reads as: - -.. code-block:: python - :linenos: - - if __name__ == '__main__': - config = Configurator() - config.begin() - config.load_zcml('configure.zcml') - config.end() - app = config.make_wsgi_app() - serve(app, host='0.0.0.0') - -Everything else is much the same. - -The ``config.load_zcml('configure.zcml')`` line tells the configurator -to load configuration declarations from the file named -``configure.zcml`` which sits next to ``helloworld.py`` on the -filesystem. Let's take a look at that ``configure.zcml`` file again: - -.. code-block:: xml - :linenos: - - <configure xmlns="http://pylonshq.com/pyramid"> - - <include package="pyramid.includes" /> - - <view - view="helloworld.hello_world" - /> - - </configure> - -Note that this file contains some XML, and that the XML contains a -``<view>`` :term:`configuration declaration` tag that references a -:term:`dotted Python name`. This dotted name refers to the -``hello_world`` function that lives in our ``helloworld`` Python -module. - -This ``<view>`` declaration tag performs the same function as the -``add_view`` method that was employed within -:ref:`imperative_configuration`. In fact, the ``<view>`` tag is -effectively a "macro" which calls the -:meth:`pyramid.config.Configurator.add_view` method on your -behalf. - -The ``<view>`` tag is an example of a :app:`Pyramid` declaration -tag. Other such tags include ``<route>`` and ``<scan>``. Each of -these tags is effectively a "macro" which calls methods of a -:class:`pyramid.config.Configurator` object on your behalf. - -Essentially, using a :term:`ZCML` file and loading it from the -filesystem allows us to put our configuration statements within this -XML file rather as declarations, rather than representing them as -method calls to a :term:`Configurator` object. Otherwise, declarative -and imperative configuration are functionally equivalent. - -Using declarative configuration has a number of benefits, the primary -benefit being that applications configured declaratively can be -*overridden* and *extended* by third parties without requiring the -third party to change application code. If you want to build a -framework or an extensible application, using declarative -configuration is a good idea. - -Declarative configuration has an obvious downside: you can't use -plain-old-Python syntax you probably already know and understand to -configure your application; instead you need to use :term:`ZCML`. - -.. index:: - single: ZCML conflict detection - -ZCML Conflict Detection -~~~~~~~~~~~~~~~~~~~~~~~ - -A minor additional feature of ZCML is *conflict detection*. If you -define two declaration tags within the same ZCML file which logically -"collide", an exception will be raised, and the application will not -start. For example, the following ZCML file has two conflicting -``<view>`` tags: - -.. code-block:: xml - :linenos: - - <configure xmlns="http://pylonshq.com/pyramid"> - - <include package="pyramid.includes" /> - - <view - view="helloworld.hello_world" - /> - - <view - view="helloworld.hello_world" - /> - - </configure> - -If you try to use this ZCML file as the source of ZCML for an -application, an error will be raised when you attempt to start the -application. This error will contain information about which tags -might have conflicted. - -.. index:: - single: helloworld (declarative) - -.. _helloworld_declarative: - -Hello World, Goodbye World (Declarative) ----------------------------------------- - -Another almost entirely equivalent mode of application configuration -exists named *declarative* configuration. :app:`Pyramid` can be -configured for the same "hello world" application "declaratively", if -so desired. - -To do so, first, create a file named ``helloworld.py``: - -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - from pyramid.response import Response - from paste.httpserver import serve - - def hello_world(request): - return Response('Hello world!') - - def goodbye_world(request): - return Response('Goodbye world!') - - if __name__ == '__main__': - config = Configurator() - config.begin() - config.load_zcml('configure.zcml') - config.end() - app = config.make_wsgi_app() - serve(app, host='0.0.0.0') - -Then create a file named ``configure.zcml`` in the same directory as -the previously created ``helloworld.py``: - -.. code-block:: xml - :linenos: - - <configure xmlns="http://pylonshq.com/pyramid"> - - <include package="pyramid.includes" /> - - <view - view="helloworld.hello_world" - /> - - <view - name="goodbye" - view="helloworld.goodbye_world" - /> - - </configure> - -This pair of files forms an application functionally equivalent to the -application we created earlier in :ref:`helloworld_imperative`. We can run -it the same way. - -.. code-block:: text - - $ python helloworld.py - serving on 0.0.0.0:8080 view at http://127.0.0.1:8080 - -Let's examine the differences between the code in that section and the code -above. In :ref:`helloworld_imperative_appconfig`, we had the following lines -within the ``if __name__ == '__main__'`` section of ``helloworld.py``: - -.. code-block:: python - :linenos: - - if __name__ == '__main__': - config = Configurator() - config.begin() - config.add_view(hello_world) - config.add_view(goodbye_world, name='goodbye') - config.end() - app = config.make_wsgi_app() - serve(app, host='0.0.0.0') - -In our "declarative" code, we've added a call to the -:meth:`pyramid.config.Configurator.load_zcml` method with -the value ``configure.zcml``, and we've removed the lines which read -``config.add_view(hello_world)`` and ``config.add_view(goodbye_world, -name='goodbye')``, so that it now reads as: - -.. code-block:: python - :linenos: - - if __name__ == '__main__': - config = Configurator() - config.begin() - config.load_zcml('configure.zcml') - config.end() - app = config.make_wsgi_app() - serve(app, host='0.0.0.0') - -Everything else is much the same. - -The ``config.load_zcml('configure.zcml')`` line tells the configurator -to load configuration declarations from the ``configure.zcml`` file -which sits next to ``helloworld.py``. Let's take a look at the -``configure.zcml`` file now: - -.. code-block:: xml - :linenos: - - <configure xmlns="http://pylonshq.com/pyramid"> - - <include package="pyramid.includes" /> - - <view - view="helloworld.hello_world" - /> - - <view - name="goodbye" - view="helloworld.goodbye_world" - /> - - </configure> - -We already understand what the view code does, because the application -is functionally equivalent to the application described in -:ref:`helloworld_imperative`, but use of :term:`ZCML` is new. Let's -break that down tag-by-tag. - -The ``<configure>`` Tag -~~~~~~~~~~~~~~~~~~~~~~~ - -The ``configure.zcml`` ZCML file contains this bit of XML: - -.. code-block:: xml - :linenos: - - <configure xmlns="http://pylonshq.com/pyramid"> - - <!-- other directives --> - - </configure> - -Because :term:`ZCML` is XML, and because XML requires a single root -tag for each document, every ZCML file used by :app:`Pyramid` must -contain a ``configure`` container directive, which acts as the root -XML tag. It is a "container" directive because its only job is to -contain other directives. - -See also :ref:`configure_directive` and :ref:`word_on_xml_namespaces`. - -The ``<include>`` Tag -~~~~~~~~~~~~~~~~~~~~~ - -The ``configure.zcml`` ZCML file contains this bit of XML within the -``<configure>`` root tag: - -.. code-block:: xml - :linenos: - - <include package="pyramid.includes" /> - -This self-closing tag instructs :app:`Pyramid` to load a ZCML file -from the Python package with the :term:`dotted Python name` -``pyramid.includes``, as specified by its ``package`` attribute. -This particular ``<include>`` declaration is required because it -actually allows subsequent declaration tags (such as ``<view>``, which -we'll see shortly) to be recognized. The ``<include>`` tag -effectively just includes another ZCML file, causing its declarations -to be executed. In this case, we want to load the declarations from -the file named ``configure.zcml`` within the -:mod:`pyramid.includes` Python package. We know we want to load -the ``configure.zcml`` from this package because ``configure.zcml`` is -the default value for another attribute of the ``<include>`` tag named -``file``. We could have spelled the include tag more verbosely, but -equivalently as: - -.. code-block:: xml - :linenos: - - <include package="pyramid.includes" - file="configure.zcml"/> - -The ``<include>`` tag that includes the ZCML statements implied by the -``configure.zcml`` file from the Python package named -:mod:`pyramid.includes` is basically required to come before any -other named declaration in an application's ``configure.zcml``. If it -is not included, subsequent declaration tags will fail to be -recognized, and the configuration system will generate an error at -startup. However, the ``<include package="pyramid.includes"/>`` -tag needs to exist only in a "top-level" ZCML file, it needn't also -exist in ZCML files *included by* a top-level ZCML file. - -See also :ref:`include_directive`. - -The ``<view>`` Tag -~~~~~~~~~~~~~~~~~~ - -The ``configure.zcml`` ZCML file contains these bits of XML *after* the -``<include>`` tag, but *within* the ``<configure>`` root tag: - -.. code-block:: xml - :linenos: - - <view - view="helloworld.hello_world" - /> - - <view - name="goodbye" - view="helloworld.goodbye_world" - /> - -These ``<view>`` declaration tags direct :app:`Pyramid` to create -two :term:`view configuration` registrations. The first ``<view>`` -tag has an attribute (the attribute is also named ``view``), which -points at a :term:`dotted Python name`, referencing the -``hello_world`` function defined within the ``helloworld`` package. -The second ``<view>`` tag has a ``view`` attribute which points at a -:term:`dotted Python name`, referencing the ``goodbye_world`` function -defined within the ``helloworld`` package. The second ``<view>`` tag -also has an attribute called ``name`` with a value of ``goodbye``. - -These effect of the ``<view>`` tag declarations we've put into our -``configure.zcml`` is functionally equivalent to the effect of lines -we've already seen in an imperatively-configured application. We're -just spelling things differently, using XML instead of Python. - -In our previously defined application, in which we added view -configurations imperatively, we saw this code: - -.. ignore-next-block -.. code-block:: python - :linenos: - - config.add_view(hello_world) - config.add_view(goodbye_world, name='goodbye') - -Each ``<view>`` declaration tag encountered in a ZCML file effectively -invokes the :meth:`pyramid.config.Configurator.add_view` -method on the behalf of the developer. Various attributes can be -specified on the ``<view>`` tag which influence the :term:`view -configuration` it creates. - -Since the relative ordering of calls to -:meth:`pyramid.config.Configurator.add_view` doesn't matter -(see the sidebar entitled *View Dispatch and Ordering* within -:ref:`adding_configuration`), the relative order of ``<view>`` tags in -ZCML doesn't matter either. The following ZCML orderings are -completely equivalent: - -.. topic:: Hello Before Goodbye - - .. code-block:: xml - :linenos: - - <view - view="helloworld.hello_world" - /> - - <view - name="goodbye" - view="helloworld.goodbye_world" - /> - -.. topic:: Goodbye Before Hello - - .. code-block:: xml - :linenos: - - <view - name="goodbye" - view="helloworld.goodbye_world" - /> - - <view - view="helloworld.hello_world" - /> - -We've now configured a :app:`Pyramid` helloworld application -declaratively. More information about this mode of configuration is -available in :ref:`declarative_configuration` and within -:ref:`zcml_reference`. - -.. _zcml_scanning: - -Scanning via ZCML ------------------ - -:term:`ZCML` can invoke a :term:`scan` via its ``<scan>`` directive. If a -ZCML file is processed that contains a scan directive, the package the ZCML -file points to is scanned. - -.. code-block:: python - :linenos: - - # helloworld.py - - from paste.httpserver import serve - from pyramid.response import Response - from pyramid.view import view_config - - @view_config() - def hello(request): - return Response('Hello') - - if __name__ == '__main__': - from pyramid.config import Configurator - config = Configurator() - config.begin() - config.load_zcml('configure.zcml') - config.end() - app = config.make_wsgi_app() - serve(app, host='0.0.0.0') - -.. code-block:: xml - :linenos: - - <configure xmlns="http://namespaces.repoze.org"> - - <!-- configure.zcml --> - - <include package="pyramid.includes"/> - <scan package="."/> - - </configure> - -See also :ref:`scan_directive`. - -Which Mode Should I Use? ------------------------- - -A combination of imperative configuration, declarative configuration -via ZCML and scanning can be used to configure any application. They -are not mutually exclusive. - -The :app:`Pyramid` authors often recommend using mostly declarative -configuration, because it's the more traditional form of configuration -used in :app:`Pyramid` applications, it can be overridden and -extended by third party deployers, and there are more examples for it -"in the wild". - -However, imperative mode configuration can be simpler to understand, -and the framework is not "opinionated" about the choice. This book -presents examples in both styles, mostly interchangeably. You can -choose the mode that best fits your brain as necessary. - -.. index:: - single: ZCML view configuration - -.. _mapping_views_using_zcml_section: - -View Configuration Via ZCML -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You may associate a view with a URL by adding :ref:`view_directive` -declarations via :term:`ZCML` in a ``configure.zcml`` file. An -example of a view declaration in ZCML is as follows: - -.. code-block:: xml - :linenos: - - <view - context=".resources.Hello" - view=".views.hello_world" - name="hello.html" - /> - -The above maps the ``.views.hello_world`` view callable function to -the following set of :term:`resource location` results: - -- A :term:`context` object which is an instance (or subclass) of the - Python class represented by ``.resources.Hello`` - -- A :term:`view name` equalling ``hello.html``. - -.. note:: Values prefixed with a period (``.``) for the ``context`` - and ``view`` attributes of a ``view`` declaration (such as those - above) mean "relative to the Python package directory in which this - :term:`ZCML` file is stored". So if the above ``view`` declaration - was made inside a ``configure.zcml`` file that lived in the - ``hello`` package, you could replace the relative ``.resources.Hello`` - with the absolute ``hello.resources.Hello``; likewise you could - replace the relative ``.views.hello_world`` with the absolute - ``hello.views.hello_world``. Either the relative or absolute form - is functionally equivalent. It's often useful to use the relative - form, in case your package's name changes. It's also shorter to - type. - -You can also declare a *default view callable* for a :term:`resource` type: - -.. code-block:: xml - :linenos: - - <view - context=".resources.Hello" - view=".views.hello_world" - /> - -A *default view callable* simply has no ``name`` attribute. For the above -registration, when a :term:`context` is found that is of the type -``.resources.Hello`` and there is no :term:`view name` associated with the -result of :term:`resource location`, the *default view callable* will be -used. In this case, it's the view at ``.views.hello_world``. - -A default view callable can alternately be defined by using the empty -string as its ``name`` attribute: - -.. code-block:: xml - :linenos: - - <view - context=".resources.Hello" - view=".views.hello_world" - name="" - /> - -You may also declare that a view callable is good for any context type -by using the special ``*`` character as the value of the ``context`` -attribute: - -.. code-block:: xml - :linenos: - - <view - context="*" - view=".views.hello_world" - name="hello.html" - /> - -This indicates that when :app:`Pyramid` identifies that the -:term:`view name` is ``hello.html`` and the context is of any type, -the ``.views.hello_world`` view callable will be invoked. - -A ZCML ``view`` declaration's ``view`` attribute can also name a -class. In this case, the rules described in :ref:`class_as_view` -apply for the class which is named. - -See :ref:`view_directive` for complete ZCML directive documentation. - -.. index:: - single: ZCML directive; route - -.. _zcml_route_configuration: - -Configuring a Route via ZCML ----------------------------- - -Instead of using the imperative :meth:`pyramid.config.Configurator.add_route` -method to add a new route, you can alternately use :term:`ZCML`. -:ref:`route_directive` statements in a :term:`ZCML` file. For example, the -following :term:`ZCML declaration` causes a route to be added to the -application. - -.. code-block:: xml - :linenos: - - <route - name="myroute" - pattern="/prefix/{one}/{two}" - view=".views.myview" - /> - -.. note:: - - Values prefixed with a period (``.``) within the values of ZCML - attributes such as the ``view`` attribute of a ``route`` mean - "relative to the Python package directory in which this - :term:`ZCML` file is stored". So if the above ``route`` - declaration was made inside a ``configure.zcml`` file that lived in - the ``hello`` package, you could replace the relative - ``.views.myview`` with the absolute ``hello.views.myview`` Either - the relative or absolute form is functionally equivalent. It's - often useful to use the relative form, in case your package's name - changes. It's also shorter to type. - -The order that routes are evaluated when declarative configuration is used -is the order that they appear relative to each other in the ZCML file. - -See :ref:`route_directive` for full ``route`` ZCML directive -documentation. - -.. _zcml_handler_configuration: - -Configuring a Handler via ZCML ------------------------------- - -Instead of using the imperative -:meth:`pyramid.config.Configurator.add_handler` method to add a new -route, you can alternately use :term:`ZCML`. :ref:`handler_directive` -statements in a :term:`ZCML` file used by your application is a sign that -you're using :term:`URL dispatch`. For example, the following :term:`ZCML -declaration` causes a route to be added to the application. - -.. code-block:: xml - :linenos: - - <handler - route_name="myroute" - pattern="/prefix/{action}" - handler=".handlers.MyHandler" - /> - -.. note:: - - Values prefixed with a period (``.``) within the values of ZCML attributes - such as the ``handler`` attribute of a ``handler`` directive mean - "relative to the Python package directory in which this :term:`ZCML` file - is stored". So if the above ``handler`` declaration was made inside a - ``configure.zcml`` file that lived in the ``hello`` package, you could - replace the relative ``.views.MyHandler`` with the absolute - ``hello.views.MyHandler`` Either the relative or absolute form is - functionally equivalent. It's often useful to use the relative form, in - case your package's name changes. It's also shorter to type. - -The order that the routes attached to handlers are evaluated when declarative -configuration is used is the order that they appear relative to each other in -the ZCML file. - -See :ref:`handler_directive` for full ``handler`` ZCML directive -documentation. - -.. index:: - triple: view; zcml; static resource - -.. _zcml_static_assets_section: - -Serving Static Assets Using ZCML --------------------------------- - -Use of the ``static`` ZCML directive makes static assets available at a name -relative to the application root URL, e.g. ``/static``. - -Note that the ``path`` provided to the ``static`` ZCML directive may be a -fully qualified :term:`asset specification`, a package-relative path, or -an *absolute path*. The ``path`` with the value ``a/b/c/static`` of a -``static`` directive in a ZCML file that resides in the "mypackage" package -will resolve to a package-qualified assets such as -``some_package:a/b/c/static``. - -Here's an example of a ``static`` ZCML directive that will serve files -up under the ``/static`` URL from the ``/var/www/static`` directory of -the computer which runs the :app:`Pyramid` application using an -absolute path. - -.. code-block:: xml - :linenos: - - <static - name="static" - path="/var/www/static" - /> - -Here's an example of a ``static`` directive that will serve files up -under the ``/static`` URL from the ``a/b/c/static`` directory of the -Python package named ``some_package`` using a fully qualified -:term:`asset specification`. - -.. code-block:: xml - :linenos: - - <static - name="static" - path="some_package:a/b/c/static" - /> - -Here's an example of a ``static`` directive that will serve files up -under the ``/static`` URL from the ``static`` directory of the Python -package in which the ``configure.zcml`` file lives using a -package-relative path. - -.. code-block:: xml - :linenos: - - <static - name="static" - path="static" - /> - -Whether you use for ``path`` a fully qualified asset specification, -an absolute path, or a package-relative path, When you place your -static files on the filesystem in the directory represented as the -``path`` of the directive, you will then be able to view the static -files in this directory via a browser at URLs prefixed with the -directive's ``name``. For instance if the ``static`` directive's -``name`` is ``static`` and the static directive's ``path`` is -``/path/to/static``, ``http://localhost:6543/static/foo.js`` will -return the file ``/path/to/static/dir/foo.js``. The static directory -may contain subdirectories recursively, and any subdirectories may -hold files; these will be resolved by the static view as you would -expect. - -While the ``path`` argument can be a number of different things, the -``name`` argument of the ``static`` ZCML directive can also be one of -a number of things: a *view name* or a *URL*. The above examples have -shown usage of the ``name`` argument as a view name. When ``name`` is -a *URL* (or any string with a slash (``/``) in it), static assets -can be served from an external webserver. In this mode, the ``name`` -is used as the URL prefix when generating a URL using -:func:`pyramid.url.static_url`. - -For example, the ``static`` ZCML directive may be fed a ``name`` -argument which is ``http://example.com/images``: - -.. code-block:: xml - :linenos: - - <static - name="http://example.com/images" - path="mypackage:images" - /> - -Because the ``static`` ZCML directive is provided with a ``name`` argument -that is the URL prefix ``http://example.com/images``, subsequent calls to -:func:`pyramid.url.static_url` with paths that start with the ``path`` -argument passed to :meth:`pyramid.url.static_url` will generate a URL -something like ``http://example.com/logo.png``. The external webserver -listening on ``example.com`` must be itself configured to respond properly to -such a request. The :func:`pyramid.url.static_url` API is discussed in more -detail later in this chapter. - -The :meth:`pyramid.config.Configurator.add_static_view` method offers -an imperative equivalent to the ``static`` ZCML directive. Use of the -``add_static_view`` imperative configuration method is completely equivalent -to using ZCML for the same purpose. See :ref:`static_assets_section` for -more information. - -.. index:: - pair: ZCML directive; asset - -.. _asset_zcml_directive: - -The ``asset`` ZCML Directive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Instead of using :meth:`pyramid.config.Configurator.override_asset` during -:term:`imperative configuration`, an equivalent ZCML directive can be used. -The ZCML ``asset`` tag is a frontend to using -:meth:`pyramid.config.Configurator.override_asset`. - -An individual :app:`Pyramid` ``asset`` ZCML statement can override a -single asset. For example: - -.. code-block:: xml - :linenos: - - <asset - to_override="some.package:templates/mytemplate.pt" - override_with="another.package:othertemplates/anothertemplate.pt" - /> - -The string value passed to both ``to_override`` and ``override_with`` -attached to an ``asset`` directive is called an "asset specification". The -colon separator in a specification separates the *package name* from the -*asset name*. The colon and the following asset name are optional. If they -are not specified, the override attempts to resolve every lookup into a -package from the directory of another package. For example: - -.. code-block:: xml - :linenos: - - <asset - to_override="some.package" - override_with="another.package" - /> - -Individual subdirectories within a package can also be overridden: - -.. code-block:: xml - :linenos: - - <asset - to_override="some.package:templates/" - override_with="another.package:othertemplates/" - /> - -If you wish to override an asset directory with another directory, you *must* -make sure to attach the slash to the end of both the ``to_override`` -specification and the ``override_with`` specification. If you fail to attach -a slash to the end of an asset specification that points to a directory, you -will get unexpected results. - -The package name in an asset specification may start with a dot, meaning that -the package is relative to the package in which the ZCML file resides. For -example: - -.. code-block:: xml - :linenos: - - <asset - to_override=".subpackage:templates/" - override_with="another.package:templates/" - /> - -See also :ref:`asset_directive`. - -.. _zcml_authorization_policy: - -Enabling an Authorization Policy Via ZCML ------------------------------------------ - -If you'd rather use :term:`ZCML` to specify an authorization policy -than imperative configuration, modify the ZCML file loaded by your -application (usually named ``configure.zcml``) to enable an -authorization policy. - -For example, to enable a policy which compares the value of an "auth ticket" -cookie passed in the request's environment which contains a reference to a -single :term:`principal` against the principals present in any :term:`ACL` -found in the resource tree when attempting to call some :term:`view`, modify -your ``configure.zcml`` to look something like this: - -.. code-block:: xml - :linenos: - - <configure xmlns="http://pylonshq.com/pyramid"> - - <!-- views and other directives before this... --> - - <authtktauthenticationpolicy - secret="iamsosecret"/> - - <aclauthorizationpolicy/> - - </configure> - -"Under the hood", these statements cause an instance of the class -:class:`pyramid.authentication.AuthTktAuthenticationPolicy` to be -injected as the :term:`authentication policy` used by this application -and an instance of the class -:class:`pyramid.authorization.ACLAuthorizationPolicy` to be -injected as the :term:`authorization policy` used by this application. - -:app:`Pyramid` ships with a number of authorization and -authentication policy ZCML directives that should prove useful. See -:ref:`authentication_policies_directives_section` and -:ref:`authorization_policies_directives_section` for more information. - -.. index:: - pair: ZCML directive; authentication policy - -.. _authentication_policies_directives_section: - -Built-In Authentication Policy ZCML Directives ----------------------------------------------- - -Instead of configuring an authentication policy and authorization -policy imperatively, :app:`Pyramid` ships with a few "pre-chewed" -authentication policy ZCML directives that you can make use of within -your application. - -``authtktauthenticationpolicy`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When this directive is used, authentication information is obtained -from an "auth ticket" cookie value, assumed to be set by a custom -login form. - -An example of its usage, with all attributes fully expanded: - -.. code-block:: xml - :linenos: - - <authtktauthenticationpolicy - secret="goshiamsosecret" - callback=".somemodule.somefunc" - cookie_name="mycookiename" - secure="false" - include_ip="false" - timeout="86400" - reissue_time="600" - max_age="31536000" - path="/" - http_only="false" - /> - -See :ref:`authtktauthenticationpolicy_directive` for details about -this directive. - -``remoteuserauthenticationpolicy`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When this directive is used, authentication information is obtained -from a ``REMOTE_USER`` key in the WSGI environment, assumed to -be set by a WSGI server or an upstream middleware component. - -An example of its usage, with all attributes fully expanded: - -.. code-block:: xml - :linenos: - - <remoteuserauthenticationpolicy - environ_key="REMOTE_USER" - callback=".somemodule.somefunc" - /> - -See :ref:`remoteuserauthenticationpolicy_directive` for detailed -information. - -``repozewho1authenticationpolicy`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When this directive is used, authentication information is obtained -from a ``repoze.who.identity`` key in the WSGI environment, assumed to -be set by :term:`repoze.who` middleware. - -An example of its usage, with all attributes fully expanded: - -.. code-block:: xml - :linenos: - - <repozewho1authenticationpolicy - identifier_name="auth_tkt" - callback=".somemodule.somefunc" - /> - -See :ref:`repozewho1authenticationpolicy_directive` for detailed -information. - -.. index:: - pair: ZCML directive; authorization policy - -.. _authorization_policies_directives_section: - -Built-In Authorization Policy ZCML Directives ---------------------------------------------- - -``aclauthorizationpolicy`` - -When this directive is used, authorization information is obtained -from :term:`ACL` objects attached to resources. - -An example of its usage, with all attributes fully expanded: - -.. code-block:: xml - :linenos: - - <aclauthorizationpolicy/> - -In other words, it has no configuration attributes; its existence in a -``configure.zcml`` file enables it. - -See :ref:`aclauthorizationpolicy_directive` for detailed information. - -.. _zcml_adding_and_overriding_renderers: - -Adding and Overriding Renderers via ZCML ----------------------------------------- - -New templating systems and serializers can be associated with :app:`Pyramid` -renderer names. To this end, configuration declarations can be made which -override an existing :term:`renderer factory` and which add a new renderer -factory. - -Adding or overriding a renderer via ZCML is accomplished via the -:ref:`renderer_directive` ZCML directive. - -For example, to add a renderer which renders views which have a -``renderer`` attribute that is a path that ends in ``.jinja2``: - -.. code-block:: xml - :linenos: - - <renderer - name=".jinja2" - factory="my.package.MyJinja2Renderer" - /> - -The ``factory`` attribute is a :term:`dotted Python name` that must -point to an implementation of a :term:`renderer factory`. - -The ``name`` attribute is the renderer name. - -Registering a Renderer Factory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -See :ref:`adding_a_renderer` for more information for the definition of a -:term:`renderer factory`. Here's an example of the registration of a simple -:term:`renderer factory` via ZCML: - -.. code-block:: xml - :linenos: - - <renderer - name="amf" - factory="my.package.MyAMFRenderer" - /> - -Adding the above ZCML to your application will allow you to use the -``my.package.MyAMFRenderer`` renderer factory implementation in view -configurations by subseqently referring to it as ``amf`` in the ``renderer`` -attribute of a :term:`view configuration`: - -.. code-block:: xml - :linenos: - - <view - view="mypackage.views.my_view" - renderer="amf" - /> - -Here's an example of the registration of a more complicated renderer -factory, which expects to be passed a filesystem path: - -.. code-block:: xml - :linenos: - - <renderer - name=".jinja2" - factory="my.package.MyJinja2Renderer" - /> - -Adding the above ZCML to your application will allow you to use the -``my.package.MyJinja2Renderer`` renderer factory implementation in -view configurations by referring to any ``renderer`` which *ends in* -``.jinja`` in the ``renderer`` attribute of a :term:`view -configuration`: - -.. code-block:: xml - :linenos: - - <view - view="mypackage.views.my_view" - renderer="templates/mytemplate.jinja2" - /> - -When a :term:`view configuration` which has a ``name`` attribute that does -contain a dot, such as ``templates/mytemplate.jinja2`` above is encountered at -startup time, the value of the name attribute is split on its final dot. The -second element of the split is typically the filename extension. This -extension is used to look up a renderer factory for the configured view. Then -the value of ``renderer`` is passed to the factory to create a renderer for the -view. In this case, the view configuration will create an instance of a -``Jinja2Renderer`` for each view configuration which includes anything ending -with ``.jinja2`` as its ``renderer`` value. The ``name`` passed to the -``Jinja2Renderer`` constructor will be whatever the user passed as -``renderer=`` to the view configuration. - -See also :ref:`renderer_directive` and -:meth:`pyramid.config.Configurator.add_renderer`. - -Overriding an Existing Renderer -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can associate more than one filename extension with the same -existing renderer implementation as necessary if you need to use a -different file extension for the same kinds of templates. For -example, to associate the ``.zpt`` extension with the Chameleon ZPT -renderer factory, use: - -.. code-block:: xml - :linenos: - - <renderer - name=".zpt" - factory="pyramid.chameleon_zpt.renderer_factory" - /> - -After you do this, :app:`Pyramid` will treat templates ending in -both the ``.pt`` and ``.zpt`` filename extensions as Chameleon ZPT -templates. - -To override the default mapping in which files with a ``.pt`` -extension are rendered via a Chameleon ZPT page template renderer, use -a variation on the following in your application's ZCML: - -.. code-block:: xml - :linenos: - - <renderer - name=".pt" - factory="my.package.pt_renderer" - /> - -After you do this, the :term:`renderer factory` in -``my.package.pt_renderer`` will be used to render templates which end -in ``.pt``, replacing the default Chameleon ZPT renderer. - -To override the default mapping in which files with a ``.txt`` -extension are rendered via a Chameleon text template renderer, use a -variation on the following in your application's ZCML: - -.. code-block:: xml - :linenos: - - <renderer - name=".txt" - factory="my.package.text_renderer" - /> - -After you do this, the :term:`renderer factory` in -``my.package.text_renderer`` will be used to render templates which -end in ``.txt``, replacing the default Chameleon text renderer. - -To associate a *default* renderer with *all* view configurations (even -ones which do not possess a ``renderer`` attribute), use a variation -on the following (ie. omit the ``name`` attribute to the renderer -tag): - -.. code-block:: xml - :linenos: - - <renderer - factory="pyramid.renderers.json_renderer_factory" - /> - -See also :ref:`renderer_directive` and -:meth:`pyramid.config.Configurator.add_renderer`. - -.. _zcml_adding_a_translation_directory: - -Adding a Translation Directory via ZCML ---------------------------------------- - -You can add a translation directory via ZCML by using the -:ref:`translationdir_directive` ZCML directive: - -.. code-block:: xml - :linenos: - - <translationdir dir="my.application:locale/"/> - -A message catalog in a translation directory added via -:ref:`translationdir_directive` will be merged into translations from -a message catalog added earlier if both translation directories -contain translations for the same locale and :term:`translation -domain`. - -See also :ref:`translationdir_directive` and -:ref:`adding_a_translation_directory`. - -.. _zcml_adding_a_locale_negotiator: - -Adding a Custom Locale Negotiator via ZCML ------------------------------------------- - -You can add a custom locale negotiator via ZCML by using the -:ref:`localenegotiator_directive` ZCML directive: - -.. code-block:: xml - :linenos: - - <localenegotiator - negotiator="my_application.my_module.my_locale_negotiator" - /> - -See also :ref:`custom_locale_negotiator` and -:ref:`localenegotiator_directive`. - -.. index:: - pair: subscriber; ZCML directive - -.. _zcml_event_listener: - -Configuring an Event Listener via ZCML --------------------------------------- - -You can configure an :term:`subscriber` by modifying your application's -``configure.zcml``. Here's an example of a bit of XML you can add to the -``configure.zcml`` file which registers the above ``mysubscriber`` function, -which we assume lives in a ``subscribers.py`` module within your application: - -.. code-block:: xml - :linenos: - - <subscriber - for="pyramid.events.NewRequest" - handler=".subscribers.mysubscriber" - /> - -See also :ref:`subscriber_directive` and :ref:`events_chapter`. - - -.. Todo -.. ---- - -.. - hooks chapter still has topics for ZCML - diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index 7caa1dcc6..7b7946aae 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -38,10 +38,11 @@ application-specific configuration settings. Reloading Templates ------------------- -When this value is true, reload templates without a restart, so you can see -changes to templates take effect immediately during development. This flag -is meaningful to Chameleon and Mako templates, as well as most third-party -template rendering extensions. +When this value is true, templates are automatically reloaded whenever +they are modified without restarting the application, so you can see +changes to templates take effect immediately during development. This +flag is meaningful to Chameleon and Mako templates, as well as most +third-party template rendering extensions. +---------------------------------+-----------------------------+ | Environment Variable Name | Config File Setting Name | @@ -335,6 +336,14 @@ with ``reload_``). on in one fell swoop, you can use affect settings that do not start with ``reload_*`` such as ``debug_notfound``. +.. note:: + Specifying configuration settings via environment variables is generally + most useful during development, where you may wish to augment or + override the more permanent settings in the configuration file. + This is useful because many of the reload and debug settings may + have performance or security (i.e., disclosure) implications + that make them undesirable in a production environment. + .. index:: single: reload_templates single: reload_assets diff --git a/docs/narr/events.rst b/docs/narr/events.rst index 06b30883f..929208083 100644 --- a/docs/narr/events.rst +++ b/docs/narr/events.rst @@ -38,52 +38,50 @@ you'll need to use the need to use the :func:`pyramid.events.subscriber` decorator to decorate a function found via a :term:`scan`. -.. topic:: Configuring an Event Listener Imperatively +Configuring an Event Listener Imperatively +------------------------------------------ - You can imperatively configure a subscriber function to be called - for some event type via the - :meth:`pyramid.config.Configurator.add_subscriber` - method (see also :term:`Configurator`): +You can imperatively configure a subscriber function to be called +for some event type via the +:meth:`~pyramid.config.Configurator.add_subscriber` +method (see also :term:`Configurator`): - .. code-block:: python - :linenos: - - from pyramid.events import NewRequest +.. code-block:: python + :linenos: - from subscribers import mysubscriber + from pyramid.events import NewRequest - # "config" below is assumed to be an instance of a - # pyramid.config.Configurator object + from subscribers import mysubscriber - config.add_subscriber(mysubscriber, NewRequest) + # "config" below is assumed to be an instance of a + # pyramid.config.Configurator object - The first argument to - :meth:`pyramid.config.Configurator.add_subscriber` is the - subscriber function (or a :term:`dotted Python name` which refers - to a subscriber callable); the second argument is the event type. + config.add_subscriber(mysubscriber, NewRequest) -.. topic:: Configuring an Event Listener Using a Decorator +The first argument to +:meth:`~pyramid.config.Configurator.add_subscriber` is the +subscriber function (or a :term:`dotted Python name` which refers +to a subscriber callable); the second argument is the event type. - You can configure a subscriber function to be called for some event - type via the :func:`pyramid.events.subscriber` function. +Configuring an Event Listener Using a Decorator +----------------------------------------------- - .. code-block:: python - :linenos: +You can configure a subscriber function to be called for some event +type via the :func:`pyramid.events.subscriber` function. - from pyramid.events import NewRequest - from pyramid.events import subscriber +.. code-block:: python + :linenos: - @subscriber(NewRequest) - def mysubscriber(event): - event.request.foo = 1 + from pyramid.events import NewRequest + from pyramid.events import subscriber - When the :func:`pyramid.subscriber` decorator is used a - :term:`scan` must be performed against the package containing the - decorated function for the decorator to have any effect. See - :func:`pyramid.subscriber` for more information. + @subscriber(NewRequest) + def mysubscriber(event): + event.request.foo = 1 -.. note:: You can also configure an event listener via ZCML. See - :ref:`zcml_event_listener`. +When the :func:`~pyramid.events.subscriber` decorator is used a +:term:`scan` must be performed against the package containing the +decorated function for the decorator to have any effect. Either of the above registration examples implies that every time the :app:`Pyramid` framework emits an event object that supplies an diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index 9802a01f6..416207584 100644 --- a/docs/narr/extending.rst +++ b/docs/narr/extending.rst @@ -3,11 +3,61 @@ Extending An Existing :app:`Pyramid` Application =================================================== -If the developer of a :app:`Pyramid` application has obeyed certain -constraints while building that application, a third party should be -able to change its behavior without needing to modify its source code. -The behavior of a :app:`Pyramid` application that obeys certain -constraints can be *overridden* or *extended* without modification. +If a :app:`Pyramid` developer has obeyed certain constraints while building +an application, a third party should be able to change the application's +behavior without needing to modify its source code. The behavior of a +:app:`Pyramid` application that obeys certain constraints can be *overridden* +or *extended* without modification. + +We'll define some jargon here for the benefit of identifying the parties +involved in such an effort. + +Developer + The original application developer. + +Integrator + Another developer who wishes to reuse the application written by the + original application developer in an unanticipated context. He may also + wish to modify the original application without changing the original + application's source code. + +The Difference Between "Extensible" and "Pluggable" Applications +---------------------------------------------------------------- + +Other web frameworks, such as :term:`Django`, advertise that they allow +developers to create "pluggable applications". They claim that if you create +an application in a certain way, it will be integratable in a sensible, +structured way into another arbitrarily-written application or project +created by a third-party developer. + +:app:`Pyramid`, as a platform, does not claim to provide such a feature. The +platform provides no guarantee that you can create an application and package +it up such that an arbitrary integrator can use it as a subcomponent in a +larger Pyramid application or project. Pyramid does not mandate the +constraints necessary for such a pattern to work satisfactorily. Because +Pyramid is not very "opinionated", developers are able to use wildly +different patterns and technologies to build an application. A given Pyramid +application may happen to be reusable by a particular third party integrator, +because the integrator and the original developer may share similar base +technology choices (such as the use of a particular relational database or +ORM). But the same application may not be reusable by a different developer, +because he has made different technology choices which are incompatible with +the original developer's. + +As a result, the concept of a "pluggable application" is left to layers built +above Pyramid, such as a "CMS" layer or "application server" layer. Such +layers are apt to provide the necessary "opinions" (such as mandating a +storage layer, a templating system, and a structured, well-documented pattern +of registering that certain URLs map to certain bits of code) which makes the +concept of a "pluggable application" possible. "Pluggable applications", +thus, should not plug in to Pyramid itself but should instead plug into a +system written atop Pyramid. + +Although it does not provide for "pluggable applications", Pyramid *does* +provide a rich set of mechanisms which allows for the extension of a single +existing application. Such features can be used by frameworks built using +Pyramid as a base. All Pyramid applications may not be *pluggable*, but all +Pyramid applications are *extensible*. .. index:: single: extensible application @@ -15,65 +65,64 @@ constraints can be *overridden* or *extended* without modification. Rules for Building An Extensible Application -------------------------------------------- -There's only one rule you need to obey if you want to build a -maximally extensible :app:`Pyramid` application: you should not use -any :term:`configuration decoration` or :term:`imperative -configuration`. This means the application developer should avoid -relying on :term:`configuration decoration` meant to be detected via -a :term:`scan`, and you mustn't configure your :app:`Pyramid` -application *imperatively* by using any code which configures the -application through methods of the :term:`Configurator` (except for -the :meth:`pyramid.config.Configurator.load_zcml` method). - -Instead, you must always use :term:`ZCML` for the equivalent -purposes. :term:`ZCML` declarations that belong to an application can be -"overridden" by integrators as necessary, but decorators and imperative code -which perform the same tasks cannot. Use only :term:`ZCML` to configure your -application if you'd like it to be extensible. See -:ref:`declarative_chapter` for information about using ZCML. +There is only one rule you need to obey if you want to build a maximally +extensible :app:`Pyramid` application: as a developer, you should factor any +overrideable :term:`imperative configuration` you've created into functions +which can be used via :meth:`pyramid.config.Configurator.include` rather than +inlined as calls to methods of a :term:`Configurator` within the ``main`` +function in your application's ``__init__.py``. For example, rather than: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + if __name__ == '__main__': + config = Configurator() + config.add_view('myapp.views.view1', name='view1') + config.add_view('myapp.views.view2', name='view2') + +You should do move the calls to ``add_view`` outside of the (non-reusable) +``if __name__ == '__main__'`` block, and into a reusable function: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + if __name__ == '__main__': + config = Configurator() + config.include(add_views) + + def add_views(config): + config.add_view('myapp.views.view1', name='view1') + config.add_view('myapp.views.view2', name='view2') + +Doing this allows an integrator to maximally reuse the configuration +statements that relate to your application by allowing him to selectively +include or disinclude the configuration functions you've created from an +"override package". + +Alternately, you can use :term:`ZCML` for the purpose of making configuration +extensible and overrideable. :term:`ZCML` declarations that belong to an +application can be overridden and extended by integrators as necessary in a +similar fashion. If you use only :term:`ZCML` to configure your application, +it will automatically be maximally extensible without any manual effort. See +:term:`pyramid_zcml` for information about using ZCML. Fundamental Plugpoints ~~~~~~~~~~~~~~~~~~~~~~ The fundamental "plug points" of an application developed using -:app:`Pyramid` are *routes*, *views*, and *resources*. Routes are -declarations made using the ZCML ``<route>`` directive. Views are -declarations made using the ZCML ``<view>`` directive (or the -``@view_config`` decorator). Resources are files that are accessed by -:app:`Pyramid` using the :term:`pkg_resources` API such as static -files and templates. - -.. index:: - single: ZCML granularity - -ZCML Granularity -~~~~~~~~~~~~~~~~ - -It's extremely helpful to third party application "extenders" (aka -"integrators") if the :term:`ZCML` that composes the configuration for -an application is broken up into separate files which do very specific -things. These more specific ZCML files can be reintegrated within the -application's main ``configure.zcml`` via ``<include -file="otherfile.zcml"/>`` declarations. When ZCML files contain sets -of specific declarations, an integrator can avoid including any ZCML -he does not want by including only ZCML files which contain the -declarations he needs. He is not forced to "accept everything" or -"use nothing". - -For example, it's often useful to put all ``<route>`` declarations in -a separate ZCML file, as ``<route>`` statements have a relative -ordering that is extremely important to the application: if an -extender wants to add a route to the "middle" of the routing table, he -will always need to disuse all the routes and cut and paste the -routing configuration into his own application. It's useful for the -extender to be able to disuse just a *single* ZCML file in this case, -accepting the remainder of the configuration from other :term:`ZCML` -files in the original application. - -Granularizing ZCML is not strictly required. An extender can always -disuse *all* your ZCML, choosing instead to copy and paste it into his -own package, if necessary. However, doing so is considerate, and -allows for the best reusability. +:app:`Pyramid` are *routes*, *views*, and *assets*. Routes are declarations +made using the :meth:`pyramid.config.Configurator.add_route` method. Views +are declarations made using the :meth:`pyramid.config.Configurator.add_view` +method. Assets are files that are +accessed by :app:`Pyramid` using the :term:`pkg_resources` API such as static +files and templates via a :term:`asset specification`. Other directives and +configurator methods also deal in routes, views, and assets. For example, +``add_handler`` directive of the ``pyramid_handlers`` package adds a single +route, and some number of views. .. index:: single: extending an existing application @@ -81,96 +130,88 @@ allows for the best reusability. Extending an Existing Application --------------------------------- -The steps for extending an existing application depend largely on -whether the application does or does not use configuration decorators -and/or imperative code. +The steps for extending an existing application depend largely on whether the +application does or does not use configuration decorators and/or imperative +code. + +If The Application Has Configuration Decorations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You've inherited a :app:`Pyramid` application which you'd like to extend or +override that uses :class:`pyramid.view.view_config` decorators or other +:term:`configuration decoration` decorators. -Extending an Application Which Possesses Configuration Decorators Or Which Does Configuration Imperatively -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If you just want to *extend* the application, you can run a :term:`scan` +against the application's package, then add additional configuration that +registers more views or routes. -If you've inherited a :app:`Pyramid` application which uses -:class:`pyramid.view.view_config` decorators or which performs -configuration imperatively, one of two things may be true: +.. code-block:: python + :linenos: + + if __name__ == '__main__': + config.scan('someotherpackage') + config.add_view('mypackage.views.myview', name='myview') -- If you just want to *extend* the application, you can write - additional ZCML that registers more views or routes, loading any - existing ZCML and continuing to use any existing imperative - configuration done by the original application. +If you want to *override* configuration in the application, you *may* need to +run :meth:`pyramid.config.Configurator.commit` after performing the scan of +the original package, then add additional configuration that registers more +views or routes which performs overrides. -- If you want to *override* configuration in the application, you - *may* need to change the source code of the original application. +.. code-block:: python + :linenos: - If the only source of trouble is the existence of - :class:`pyramid.view.view_config` decorators, you can just prevent a - :term:`scan` from happening (by omitting the ``<scan>`` declaration - from ZCML or omitting any call to the - :meth:`pyramid.config.Configurator.scan` method). This - will cause the decorators to do nothing. At this point, you will - need to convert all the configuration done in decorators into - equivalent :term:`ZCML` and add that ZCML to a separate Python - package as described in :ref:`extending_the_application`. + if __name__ == '__main__': + config.scan('someotherpackage') + config.commit() + config.add_view('mypackage.views.myview', name='myview' - If the source of trouble is configuration done imperatively in a - function called during application startup, you'll need to change - the code: convert imperative configuration statements into - equivalent :term:`ZCML` declarations. +Once this is done, you should be able to extend or override the application +like any other (see :ref:`extending_the_application`). -Once this is done, you should be able to extend or override the -application like any other (see :ref:`extending_the_application`). +You can alternately just prevent a :term:`scan` from happening (by omitting +any call to the :meth:`pyramid.config.Configurator.scan` method). This will +cause the decorators attached to objects in the target application to do +nothing. At this point, you will need to convert all the configuration done +in decorators into equivalent imperative configuration or ZCML and add that +configuration or ZCML to a separate Python package as described in +:ref:`extending_the_application`. .. _extending_the_application: -Extending an Application Which Does Not Possess Configuration Decorators or Imperative Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To extend or override the behavior of an existing application, you -will need to write some :term:`ZCML`, and perhaps some implementations -of the types of things you'd like to override (such as views), which -are referred to within that ZCML. - -The general pattern for extending an existing application looks -something like this: - -- Create a new Python package. The easiest way to do this is to - create a new :app:`Pyramid` application using the "paster" - template mechanism. See :ref:`creating_a_project` for more - information. - -- Install the new package into the same Python environment as the - original application (e.g. ``python setup.py develop`` or ``python - setup.py install``). - -- Change the ``configure.zcml`` in the new package to include the - original :app:`Pyramid` application's ``configure.zcml`` via an - include statement, e.g. ``<include package="theoriginalapp"/>``. - Alternately, if the original application writer anticipated - overriding some things and not others, instead of including the - "main" ``configure.zcml`` of the original application, include only - specific ZCML files from the original application using the ``file`` - attribute of the ``<include>`` statement, e.g. ``<include - package="theoriginalapp" file="views.zcml"/>``. - -- On a line in the new package's ``configure.zcml`` file that falls - after (XML-ordering-wise) all the ``include`` statements of the original - package ZCML, put an ``includeOverrides`` statement which identifies - *another* ZCML file within the new package (for example - ``<includeOverrides file="overrides.zcml"/>``. - -- Create an ``overrides.zcml`` file within the new package. The - statements in the ``overrides.zcml`` file will override any ZCML - statements made within the original application (such as view - declarations). - -- Create Python files containing views and other overridden elements, - such as templates and static resources as necessary, and wire these - up using ZCML registrations within the ``overrides.zcml`` file. - These registrations may extend or override the original view - registrations. See :ref:`overriding_views`, - :ref:`overriding_routes` and :ref:`overriding_resources`. +Extending the Application +~~~~~~~~~~~~~~~~~~~~~~~~~ + +To extend or override the behavior of an existing application, you will need +to create a new package which includes the configuration of the old package, +and you'll perhaps need to create implementations of the types of things +you'd like to override (such as views), which are referred to within the +original package. -- In the ``__init__.py`` of the new package, load the ``configure.zcml`` file - of the new package using the - :meth:`pyramid.config.Configurator.load_zcml` method. +The general pattern for extending an existing application looks something +like this: + +- Create a new Python package. The easiest way to do this is to create a new + :app:`Pyramid` application using the "paster" template mechanism. See + :ref:`creating_a_project` for more information. + +- In the new package, create Python files containing views and other + overridden elements, such as templates and static assets as necessary. + +- Install the new package into the same Python environment as the original + application (e.g. ``python setup.py develop`` or ``python setup.py + install``). + +- Change the ``main`` function in the new package's ``__init__py`` to include + the original :app:`Pyramid` application's configuration functions via + :meth:`pyramid.config.Configurator.include` statements or a :term:`scan`. + +- Wire the new views and assets created in the new package up using + imperative registrations within the ``main`` function of the + ``__init__.py`` file of the new application. These wiring should happen + *after* including the configuration functions of the old application. + These registrations will extend or override any registrations performed by + the original application. See :ref:`overriding_views`, + :ref:`overriding_routes` and :ref:`overriding_resources`. .. index:: pair: overriding; views @@ -180,26 +221,44 @@ something like this: Overriding Views ~~~~~~~~~~~~~~~~~ -The ZCML ``<view>`` declarations you make which *override* application -behavior will usually have the same ``context`` and ``name`` (and -:term:`predicate` attributes, if used) as the original. These -``<view>`` declarations will point at "new" view code. The new view -code itself will usually be cut-n-paste copies of view callables from -the original application with slight tweaks. For example: +The :term:`view configuration` declarations you make which *override* +application behavior will usually have the same :term:`view predicate` +attributes as the original you wish to override. These ``<view>`` +declarations will point at "new" view code, in the override package you've +created. The new view code itself will usually be cut-n-paste copies of view +callables from the original application with slight tweaks. + +For example, if the original application has the following +``configure_views`` configuration method: + +.. code-block:: python + :linenos: + + def configure_views(config): + config.add_view('theoriginalapp.views.theview', name='theview') -.. code-block:: xml +You can override the first view configuration statement made by +``configure_views`` within the override package, after loading the original +configuration function: + +.. code-block:: python :linenos: - <view - context="theoriginalapplication.resources.SomeResource" - name="theview" - view=".views.a_view_that_does_something_slightly_different" - /> + from pyramid.config import Configurator + from originalapp import configure_views + + if __name == '__main__': + config = Configurator() + config.include(configure_views) + config.add_view('theoverrideapp.views.theview', name='theview') + +In this case, the ``theoriginalapp.views.theview`` view will never be +executed. Instead, a new view, ``theoverrideapp.views.theview`` will be +executed instead, when request circumstances dictate. -A similar pattern can be used to *extend* the application with ``<view>`` -declarations. Just register a new view against some existing resource type -(using ``context``) and make sure the URLs it implies are available on some -other page rendering. +A similar pattern can be used to *extend* the application with ``add_view`` +declarations. Just register a new view against some other set of predicates +to make sure the URLs it implies are available on some other page rendering. .. index:: pair: overriding; routes @@ -209,48 +268,27 @@ other page rendering. Overriding Routes ~~~~~~~~~~~~~~~~~ -Route setup is currently typically performed in a sequence of ordered -ZCML ``<route>`` declarations. Because these declarations are ordered -relative to each other, and because this ordering is typically -important, you should retain the relative ordering of these -declarations when performing an override. Typically, this means -*copying* all the ``<route>`` declarations into an external ZCML file -and changing them as necessary. Then disinclude any ZCML from the -original application which contains the original declarations. +Route setup is currently typically performed in a sequence of ordered calls +to :meth:`~pyramid.config.Configurator.add_route`. Because these calls are +ordered relative to each other, and because this ordering is typically +important, you should retain their relative ordering when performing an +override. Typically, this means *copying* all the ``add_route`` statements +into the override package's file and changing them as necessary. Then +disinclude any ``add_route`` statements from the original application. .. index:: - pair: overriding; resources + pair: overriding; assets .. _overriding_resources: -Overriding Resources -~~~~~~~~~~~~~~~~~~~~ - -"Resource" files are static files on the filesystem that are -accessible within a Python *package*. An entire chapter is devoted to -resources: :ref:`resources_chapter`. Within this chapter is a section -named :ref:`overriding_resources_section`. This section of that -chapter describes in detail how to override package resources with -other resources by using :term:`ZCML` ``<resource>`` declarations. Add -such ``<resource>`` declarations to your override package's -``configure.zcml`` to perform overrides. - -.. index:: - single: ZCML inclusion - -Dealing With ZCML Inclusions ----------------------------- - -Sometimes it's possible to include only certain ZCML files from an -application that contain only the registrations you really need, -omitting others. But sometimes it's not. For brute force purposes, -when you're getting ``view`` or ``route`` registrations that you don't -actually want in your overridden application, it's always appropriate -to just *not include* any ZCML file from the overridden application. -Instead, just cut and paste the entire contents of the -``configure.zcml`` (and any ZCML file included by the overridden -application's ``configure.zcml``) into your own package and omit the -``<include package=""/>`` ZCML declaration in the overriding package's -``configure.zcml``. - +Overriding Assets +~~~~~~~~~~~~~~~~~ +Assets are files on the filesystem that are accessible within a Python +*package*. An entire chapter is devoted to assets: :ref:`assets_chapter`. +Within this chapter is a section named :ref:`overriding_assets_section`. +This section of that chapter describes in detail how to override package +assets with other assets by using the +:meth:`pyramid.config.Configurator.override_asset` method. Add such +``override_asset`` calls to your override package's ``__init__.py`` to +perform overrides. diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index cb1e54b19..f5adad905 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -7,13 +7,6 @@ In this chapter, we will walk through the creation of a tiny :app:`Pyramid` application. After we're finished creating the application, we'll explain in more detail how it works. -.. note:: - - If you're a "theory-first" kind of person, you might choose to read - :ref:`resourcelocation_chapter` and :ref:`views_chapter` before diving into - the code that follows, but it's not necessary if -- like many programmers - -- you're willing to "go with the flow". - .. _helloworld_imperative: Hello World, Goodbye World @@ -55,6 +48,8 @@ When port 8080 is visited by a browser on the root URL (``/``), the server will simply serve up the text "Hello world!" When visited by a browser on the URL ``/goodbye``, the server will serve up the text "Goodbye world!" +Press ``Ctrl-C`` to stop the application. + Now that we have a rudimentary understanding of what the application does, let's examine it piece-by-piece. @@ -120,7 +115,7 @@ A view callable is required to return a :term:`response` object because a response object has all the information necessary to formulate an actual HTTP response; this object is then converted to text by the upstream :term:`WSGI` server and sent back to the requesting browser. To return a response, each -view callable creates an instance of the :class:`pyramid.response.Response` +view callable creates an instance of the :class:`~pyramid.response.Response` class. In the ``hello_world`` function, the string ``'Hello world!'`` is passed to the ``Response`` constructor as the *body* of the response. In the ``goodbye_world`` function, the string ``'Goodbye world!'`` is passed. @@ -245,7 +240,7 @@ predicates) is always invoked. In this application, :app:`Pyramid` chooses the most specific view callable based only on view :term:`predicate` applicability. The ordering of calls to -:meth:`pyramid.config.Configurator.add_view` is never very important. We can +:meth:`~pyramid.config.Configurator.add_view` is never very important. We can register ``goodbye_world`` first and ``hello_world`` second; :app:`Pyramid` will still give us the most specific callable when a request is dispatched to it. @@ -320,11 +315,11 @@ References ---------- For more information about the API of a :term:`Configurator` object, -see :class:`pyramid.config.Configurator` . +see :class:`~pyramid.config.Configurator` . For more information about :term:`view configuration`, see -:ref:`views_chapter`. +:ref:`view_config_chapter`. An example of using *declarative* configuration (:term:`ZCML`) instead of imperative configuration to create a similar "hello world" is available -within :ref:`declarative_configuration`. +within the documentation for :term:`pyramid_zcml`. diff --git a/docs/narr/flash.rst b/docs/narr/flash.rst deleted file mode 100644 index d41c2cdaf..000000000 --- a/docs/narr/flash.rst +++ /dev/null @@ -1,107 +0,0 @@ -.. _flash_chapter: - -Flash Messages -============== - -"Flash messages" are simply a queue of message strings stored in the -:term:`session`. To use flash messaging, you must enable a :term:`session -factory` as described in :ref:`using_the_default_session_factory` or -:ref:`using_alternate_session_factories`. - -Flash messaging has two main uses: to display a status message only once to -the user after performing an internal redirect, and to allow generic code to -log messages for single-time display without having direct access to an HTML -template. The user interface consists of a number of methods of the -:term:`session` object. - -Using the ``session.flash`` Method ----------------------------------- - -To add a message to a flash message queue, use a session object's ``flash`` -method: - -.. code-block:: python - :linenos: - - request.session.flash('mymessage') - -The ``.flash`` method appends a message to a flash queue, creating the queue -if necessary. - -``.flash`` accepts three arguments: - -.. method:: flash(message, queue='', allow_duplicate=True) - -The ``message`` argument is required. It represents a message you wish to -later display to a user. It is usually a string but the ``message`` you -provide is not modified in any way. - -The ``queue`` argument allows you to choose a queue to which to append the -message you provide. This can be used to push different kinds of messages -into flash storage for later display in different places on a page. You cam -pass any name for your queue, but it must be a string. The default value is -the empty string, which chooses the default queue. Each queue is independent, -and can be popped by ``pop_flash`` or examined via ``peek_flash`` separately. -``queue`` defaults to the empty string. The empty string represents the -default flash message queue. - -.. code-block:: python - - request.session.flash(msg, 'myappsqueue') - -The ``allow_duplicate`` argument, which defaults to ``True``. If this is -``False``, if you attempt to add a message to a queue which is already -present in the queue, it will not be added. - -Using the ``session.pop_flash`` Method --------------------------------------- - -Once one or more messages has been added to a flash queue by the -``session.flash`` API, the ``session.pop_flash`` API can be used to pop that -queue and return it for use. - -To pop a particular queue of messages from the flash object, use the session -object's ``pop_flash`` method. - -.. code-block:: python - :linenos: - - >>> 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. - -.. code-block:: python - :linenos: - - >>> request.session.flash('info message') - >>> request.session.pop_flash() - ['info message'] - >>> request.session.pop_flash() - [] - -The object returned from ``pop_flash`` is a list. - -Using the ``session.pop_flash`` Method --------------------------------------- - -Once one or more messages has been added to a flash queue by the -``session.flash`` API, the ``session.peek_flash`` API can be used to "peek" -at that queue. Unlike ``session.pop_flash``, the queue is not popped from -flash storage. - -.. code-block:: python - :linenos: - - >>> 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() - [] diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index aaf809f2e..227db2f0f 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -3,8 +3,8 @@ Using Hooks =========== -"Hooks" can be used to influence the behavior of the :app:`Pyramid` -framework in various ways. +"Hooks" can be used to influence the behavior of the :app:`Pyramid` framework +in various ways. .. index:: single: not found view @@ -14,61 +14,38 @@ framework in various ways. Changing the Not Found View --------------------------- -When :app:`Pyramid` can't map a URL to view code, it invokes a -:term:`not found view`, which is a :term:`view callable`. A default -notfound view exists. The default not found view can be overridden -through application configuration. This override can be done via -:term:`imperative configuration` or :term:`ZCML`. +When :app:`Pyramid` can't map a URL to view code, it invokes a :term:`not +found view`, which is a :term:`view callable`. A default notfound view +exists. The default not found view can be overridden through application +configuration. -The :term:`not found view` callable is a view callable like any other. -The :term:`view configuration` which causes it to be a "not found" -view consists only of naming the :exc:`pyramid.exceptions.NotFound` -class as the ``context`` of the view configuration. +The :term:`not found view` callable is a view callable like any other. The +:term:`view configuration` which causes it to be a "not found" view consists +only of naming the :exc:`pyramid.exceptions.NotFound` class as the +``context`` of the view configuration. -.. topic:: Using Imperative Configuration +If your application uses :term:`imperative configuration`, you can replace +the Not Found view by using the :meth:`pyramid.config.Configurator.add_view` +method to register an "exception view": - If your application uses :term:`imperative configuration`, you can - replace the Not Found view by using the - :meth:`pyramid.config.Configurator.add_view` method to - register an "exception view": - - .. code-block:: python - :linenos: - - from pyramid.exceptions import NotFound - from helloworld.views import notfound_view - config.add_view(notfound_view, context=NotFound) - - Replace ``helloworld.views.notfound_view`` with a reference to the - Python :term:`view callable` you want to use to represent the Not - Found view. - -.. topic:: Using ZCML - - If your application uses :term:`ZCML`, you can replace the Not Found - view by placing something like the following ZCML in your - ``configure.zcml`` file. - - .. code-block:: xml - :linenos: +.. code-block:: python + :linenos: - <view - view="helloworld.views.notfound_view" - context="pyramid.exceptions.NotFound" - /> + from pyramid.exceptions import NotFound + from helloworld.views import notfound_view + config.add_view(notfound_view, context=NotFound) - Replace ``helloworld.views.notfound_view`` with the Python dotted name - to the notfound view you want to use. +Replace ``helloworld.views.notfound_view`` with a reference to the +:term:`view callable` you want to use to represent the Not Found view. -Like any other view, the notfound view must accept at least a -``request`` parameter, or both ``context`` and ``request``. The -``request`` is the current :term:`request` representing the denied -action. The ``context`` (if used in the call signature) will be the -instance of the :exc:`pyramid.exceptions.NotFound` exception that -caused the view to be called. +Like any other view, the notfound view must accept at least a ``request`` +parameter, or both ``context`` and ``request``. The ``request`` is the +current :term:`request` representing the denied action. The ``context`` (if +used in the call signature) will be the instance of the +:exc:`~pyramid.exceptions.NotFound` exception that caused the view to be +called. -Here's some sample code that implements a minimal NotFound view -callable: +Here's some sample code that implements a minimal NotFound view callable: .. code-block:: python :linenos: @@ -80,7 +57,7 @@ callable: .. note:: When a NotFound view callable is invoked, it is passed a :term:`request`. The ``exception`` attribute of the request will - be an instance of the :exc:`pyramid.exceptions.NotFound` + be an instance of the :exc:`~pyramid.exceptions.NotFound` exception that caused the not found view to be called. The value of ``request.exception.args[0]`` will be a value explaining why the not found error was raised. This message will be different when @@ -90,7 +67,7 @@ callable: .. warning:: When a NotFound view callable accepts an argument list as described in :ref:`request_and_context_view_definitions`, the ``context`` passed as the first argument to the view callable will be the - :exc:`pyramid.exceptions.NotFound` exception instance. If available, the + :exc:`~pyramid.exceptions.NotFound` exception instance. If available, the resource context will still be available as ``request.context``. .. index:: @@ -101,59 +78,36 @@ callable: Changing the Forbidden View --------------------------- -When :app:`Pyramid` can't authorize execution of a view based on -the :term:`authorization policy` in use, it invokes a :term:`forbidden -view`. The default forbidden response has a 401 status code and is -very plain, but the view which generates it can be overridden as -necessary using either :term:`imperative configuration` or -:term:`ZCML`. +When :app:`Pyramid` can't authorize execution of a view based on the +:term:`authorization policy` in use, it invokes a :term:`forbidden view`. +The default forbidden response has a 403 status code and is very plain, but +the view which generates it can be overridden as necessary. -The :term:`forbidden view` callable is a view callable like any other. -The :term:`view configuration` which causes it to be a "not found" -view consists only of naming the :exc:`pyramid.exceptions.Forbidden` -class as the ``context`` of the view configuration. +The :term:`forbidden view` callable is a view callable like any other. The +:term:`view configuration` which causes it to be a "not found" view consists +only of naming the :exc:`pyramid.exceptions.Forbidden` class as the +``context`` of the view configuration. -.. topic:: Using Imperative Configuration +You can replace the forbidden view by using the +:meth:`pyramid.config.Configurator.add_view` method to register an "exception +view": - If your application uses :term:`imperative configuration`, you can - replace the Forbidden view by using the - :meth:`pyramid.config.Configurator.add_view` method to - register an "exception view": - - .. code-block:: python - :linenos: - - from helloworld.views import forbidden_view - from pyramid.exceptions import Forbidden - config.add_view(forbidden_view, context=Forbidden) - - Replace ``helloworld.views.forbidden_view`` with a reference to the - Python :term:`view callable` you want to use to represent the - Forbidden view. - -.. topic:: Using ZCML - - If your application uses :term:`ZCML`, you can replace the - Forbidden view by placing something like the following ZCML in your - ``configure.zcml`` file. - - .. code-block:: xml - :linenos: +.. code-block:: python + :linenos: - <view - view="helloworld.views.notfound_view" - context="pyramid.exceptions.Forbidden" - /> + from helloworld.views import forbidden_view + from pyramid.exceptions import Forbidden + config.add_view(forbidden_view, context=Forbidden) - Replace ``helloworld.views.forbidden_view`` with the Python - dotted name to the forbidden view you want to use. +Replace ``helloworld.views.forbidden_view`` with a reference to the Python +:term:`view callable` you want to use to represent the Forbidden view. -Like any other view, the forbidden view must accept at least a -``request`` parameter, or both ``context`` and ``request``. The -``context`` (available as ``request.context`` if you're using the -request-only view argument pattern) is the context found by the router -when the view invocation was denied. The ``request`` is the current -:term:`request` representing the denied action. +Like any other view, the forbidden view must accept at least a ``request`` +parameter, or both ``context`` and ``request``. The ``context`` (available +as ``request.context`` if you're using the request-only view argument +pattern) is the context found by the router when the view invocation was +denied. The ``request`` is the current :term:`request` representing the +denied action. Here's some sample code that implements a minimal forbidden view: @@ -161,184 +115,37 @@ Here's some sample code that implements a minimal forbidden view: :linenos: from pyramid.views import view_config + from pyramid.response import Response - @view_config(renderer='templates/login_form.pt') def forbidden_view(request): - return {} + return Response('forbidden') .. note:: When a forbidden view callable is invoked, it is passed a :term:`request`. The ``exception`` attribute of the request will - be an instance of the :exc:`pyramid.exceptions.Forbidden` + be an instance of the :exc:`~pyramid.exceptions.Forbidden` exception that caused the forbidden view to be called. The value of ``request.exception.args[0]`` will be a value explaining why the forbidden was raised. This message will be different when the ``debug_authorization`` environment setting is true than it is when it is false. -.. warning:: the default forbidden view sends a response with a ``401 - Unauthorized`` status code for backwards compatibility reasons. - You can influence the status code of Forbidden responses by using - an alternate forbidden view. For example, it would make sense to - return a response with a ``403 Forbidden`` status code. - -.. index:: - single: traverser - -.. _changing_the_traverser: - -Changing the Traverser ----------------------- - -The default :term:`traversal` algorithm that :app:`Pyramid` uses is -explained in :ref:`traversal_algorithm`. Though it is rarely -necessary, this default algorithm can be swapped out selectively for a -different traversal pattern via configuration. - -Use an ``adapter`` stanza in your application's ``configure.zcml`` to -change the default traverser: - -.. code-block:: xml - :linenos: - - <adapter - factory="myapp.traversal.Traverser" - provides="pyramid.interfaces.ITraverser" - for="*" - /> - -In the example above, ``myapp.traversal.Traverser`` is assumed to be -a class that implements the following interface: - -.. code-block:: python - :linenos: - - class Traverser(object): - def __init__(self, root): - """ Accept the root object returned from the root factory """ - - def __call__(self, request): - """ Return a dictionary with (at least) the keys ``root``, - ``context``, ``view_name``, ``subpath``, ``traversed``, - ``virtual_root``, and ``virtual_root_path``. These values are - typically the result of a resource tree traversal. ``root`` - is the physical root object, ``context`` will be a resource - object, ``view_name`` will be the view name used (a Unicode - name), ``subpath`` will be a sequence of Unicode names that - followed the view name but were not traversed, ``traversed`` - will be a sequence of Unicode names that were traversed - (including the virtual root path, if any) ``virtual_root`` - will be a resource object representing the virtual root (or the - physical root if traversal was not performed), and - ``virtual_root_path`` will be a sequence representing the - virtual root path (a sequence of Unicode names) or None if - traversal was not performed. - - Extra keys for special purpose functionality can be added as - necessary. - - All values returned in the dictionary will be made available - as attributes of the ``request`` object. - """ - -More than one traversal algorithm can be active at the same time. For -instance, if your :term:`root factory` returns more than one type of -object conditionally, you could claim that an alternate traverser -adapter is ``for`` only one particular class or interface. When the -root factory returned an object that implemented that class or -interface, a custom traverser would be used. Otherwise, the default -traverser would be used. For example: - -.. code-block:: xml - :linenos: - - <adapter - factory="myapp.traversal.Traverser" - provides="pyramid.interfaces.ITraverser" - for="myapp.resources.MyRoot" - /> - -If the above stanza was added to a ``configure.zcml`` file, -:app:`Pyramid` would use the ``myapp.traversal.Traverser`` only -when the application :term:`root factory` returned an instance of the -``myapp.resources.MyRoot`` object. Otherwise it would use the default -:app:`Pyramid` traverser to do traversal. - .. index:: - single: url generator - -Changing How :mod:`pyramid.url.resource_url` Generates a URL ------------------------------------------------------------- - -When you add a traverser as described in :ref:`changing_the_traverser`, it's -often convenient to continue to use the :func:`pyramid.url.resource_url` API. -However, since the way traversal is done will have been modified, the URLs it -generates by default may be incorrect. - -If you've added a traverser, you can change how -:func:`pyramid.url.resource_url` generates a URL for a specific type of -resource by adding an adapter stanza for -:class:`pyramid.interfaces.IContextURL` to your application's -``configure.zcml``: - -.. code-block:: xml - :linenos: - - <adapter - factory="myapp.traversal.URLGenerator" - provides="pyramid.interfaces.IContextURL" - for="myapp.resources.MyRoot *" - /> - -In the above example, the ``myapp.traversal.URLGenerator`` class will -be used to provide services to :func:`pyramid.url.resource_url` any -time the :term:`context` passed to ``resource_url`` is of class -``myapp.resources.MyRoot``. The asterisk following represents the type -of interface that must be possessed by the :term:`request` (in this -case, any interface, represented by asterisk). - -The API that must be implemented by a class that provides -:class:`pyramid.interfaces.IContextURL` is as follows: - -.. code-block:: python - :linenos: - - from zope.interface import Interface - - class IContextURL(Interface): - """ An adapter which deals with URLs related to a context. - """ - def __init__(self, context, request): - """ Accept the context and request """ - - def virtual_root(self): - """ Return the virtual root object related to a request and the - current context""" - - def __call__(self): - """ Return a URL that points to the context """ - -The default context URL generator is available for perusal as the -class :class:`pyramid.traversal.TraversalContextURL` in the -`traversal module -<http://github.com/Pylons/pyramid/blob/master/pyramid/traversal.py>`_ of -the :term:`Pylons` GitHub Pyramid repository. + single: request factory .. _changing_the_request_factory: Changing the Request Factory ---------------------------- -Whenever :app:`Pyramid` handles a :term:`WSGI` request, it creates -a :term:`request` object based on the WSGI environment it has been -passed. By default, an instance of the -:class:`pyramid.request.Request` class is created to represent the -request object. +Whenever :app:`Pyramid` handles a :term:`WSGI` request, it creates a +:term:`request` object based on the WSGI environment it has been passed. By +default, an instance of the :class:`pyramid.request.Request` class is created +to represent the request object. -The class (aka "factory") that :app:`Pyramid` uses to create a -request object instance can be changed by passing a -``request_factory`` argument to the constructor of the -:term:`configurator`. This argument can be either a callable or a -:term:`dotted Python name` representing a callable. +The class (aka "factory") that :app:`Pyramid` uses to create a request object +instance can be changed by passing a ``request_factory`` argument to the +constructor of the :term:`configurator`. This argument can be either a +callable or a :term:`dotted Python name` representing a callable. .. code-block:: python :linenos: @@ -350,24 +157,9 @@ request object instance can be changed by passing a config = Configurator(request_factory=MyRequest) -The same ``MyRequest`` class can alternately be registered via ZCML as -a request factory through the use of the ZCML ``utility`` directive. -In the below, we assume it lives in a package named -``mypackage.mymodule``. - -.. code-block:: xml - :linenos: - - <utility - component="mypackage.mymodule.MyRequest" - provides="pyramid.interfaces.IRequestFactory" - /> - -Lastly, if you're doing imperative configuration, and you'd rather do -it after you've already constructed a :term:`configurator` it can also -be registered via the -:meth:`pyramid.config.Configurator.set_request_factory` -method: +If you're doing imperative configuration, and you'd rather do it after you've +already constructed a :term:`configurator` it can also be registered via the +:meth:`pyramid.config.Configurator.set_request_factory` method: .. code-block:: python :linenos: @@ -381,26 +173,27 @@ method: config = Configurator() config.set_request_factory(MyRequest) +.. index:: + single: renderer globals + .. _adding_renderer_globals: Adding Renderer Globals ----------------------- -Whenever :app:`Pyramid` handles a request to perform a rendering -(after a view with a ``renderer=`` configuration attribute is invoked, -or when the any of the methods beginning with ``render`` within the -:mod:`pyramid.renderers` module are called), *renderer globals* can -be injected into the *system* values sent to the renderer. By -default, no renderer globals are injected, and the "bare" system -values (such as ``request``, ``context``, and ``renderer_name``) are -the only values present in the system dictionary passed to every -renderer. - -A callback that :app:`Pyramid` will call every time a renderer is -invoked can be added by passing a ``renderer_globals_factory`` -argument to the constructor of the :term:`configurator`. This -callback can either be a callable object or a :term:`dotted Python -name` representing such a callable. +Whenever :app:`Pyramid` handles a request to perform a rendering (after a +view with a ``renderer=`` configuration attribute is invoked, or when the any +of the methods beginning with ``render`` within the :mod:`pyramid.renderers` +module are called), *renderer globals* can be injected into the *system* +values sent to the renderer. By default, no renderer globals are injected, +and the "bare" system values (such as ``request``, ``context``, and +``renderer_name``) are the only values present in the system dictionary +passed to every renderer. + +A callback that :app:`Pyramid` will call every time a renderer is invoked can +be added by passing a ``renderer_globals_factory`` argument to the +constructor of the :term:`configurator`. This callback can either be a +callable object or a :term:`dotted Python name` representing such a callable. .. code-block:: python :linenos: @@ -411,30 +204,15 @@ name` representing such a callable. config = Configurator( renderer_globals_factory=renderer_globals_factory) -Such a callback must accept a single positional argument (notionally -named ``system``) which will contain the original system values. It -must return a dictionary of values that will be merged into the system -dictionary. See :ref:`renderer_system_values` for description of the -values present in the system dictionary. - -A renderer globals factory can alternately be registered via ZCML as a -through the use of the ZCML ``utility`` directive. In the below, we -assume a ``renderers_globals_factory`` function lives in a package -named ``mypackage.mymodule``. - -.. code-block:: xml - :linenos: - - <utility - component="mypackage.mymodule.renderer_globals_factory" - provides="pyramid.interfaces.IRendererGlobalsFactory" - /> +Such a callback must accept a single positional argument (notionally named +``system``) which will contain the original system values. It must return a +dictionary of values that will be merged into the system dictionary. See +:ref:`renderer_system_values` for description of the values present in the +system dictionary. -Lastly, if you're doing imperative configuration, and you'd rather do -it after you've already constructed a :term:`configurator` it can also -be registered via the -:meth:`pyramid.config.Configurator.set_renderer_globals_factory` -method: +If you're doing imperative configuration, and you'd rather do it after you've +already constructed a :term:`configurator` it can also be registered via the +:meth:`pyramid.config.Configurator.set_renderer_globals_factory` method: .. code-block:: python :linenos: @@ -450,6 +228,9 @@ method: Another mechanism which allows event subscribers to add renderer global values exists in :ref:`beforerender_event`. +.. index:: + single: before render event + .. _beforerender_event: Using The Before Render Event @@ -472,8 +253,8 @@ that can be used for this purpose. For example: An object of this type is sent as an event just before a :term:`renderer` is invoked (but *after* the application-level renderer globals factory added via -:class:`pyramid.config.Configurator.set_renderer_globals_factory`, if -any, has injected its own keys into the renderer globals dictionary). +:class:`~pyramid.config.Configurator.set_renderer_globals_factory`, if any, +has injected its own keys into the renderer globals dictionary). If a subscriber attempts to add a key that already exist in the renderer globals dictionary, a :exc:`KeyError` is raised. This limitation is enforced @@ -482,27 +263,30 @@ keys added to the renderer globals dictionary by all :class:`pyramid.events.BeforeRender` subscribers and renderer globals factories must be unique. -See the API documentation for the :class:`pyramid.events.BeforeRender` event +See the API documentation for the :class:`~pyramid.events.BeforeRender` event interface at :class:`pyramid.interfaces.IBeforeRender`. Another mechanism which allows event subscribers more control when adding renderer global values exists in :ref:`adding_renderer_globals`. +.. index:: + single: response callback + .. _using_response_callbacks: Using Response Callbacks ------------------------ -Unlike many other web frameworks, :app:`Pyramid` does not eagerly -create a global response object. Adding a :term:`response callback` -allows an application to register an action to be performed against a -response object once it is created, usually in order to mutate it. +Unlike many other web frameworks, :app:`Pyramid` does not eagerly create a +global response object. Adding a :term:`response callback` allows an +application to register an action to be performed against a response object +once it is created, usually in order to mutate it. -The :meth:`pyramid.request.Request.add_response_callback` method is -used to register a response callback. +The :meth:`pyramid.request.Request.add_response_callback` method is used to +register a response callback. -A response callback is a callable which accepts two positional -parameters: ``request`` and ``response``. For example: +A response callback is a callable which accepts two positional parameters: +``request`` and ``response``. For example: .. code-block:: python :linenos: @@ -515,35 +299,38 @@ parameters: ``request`` and ``response``. For example: No response callback is called if an unhandled exception happens in application code, or if the response object returned by a :term:`view -callable` is invalid. Response callbacks *are*, however, invoked when -a :term:`exception view` is rendered successfully: in such a case, the -:attr:`request.exception` attribute of the request when it enters a -response callback will be an exception object instead of its default -value of ``None``. +callable` is invalid. Response callbacks *are*, however, invoked when a +:term:`exception view` is rendered successfully: in such a case, the +:attr:`request.exception` attribute of the request when it enters a response +callback will be an exception object instead of its default value of +``None``. Response callbacks are called in the order they're added (first-to-most-recently-added). All response callbacks are called *after* -the :class:`pyramid.events.NewResponse` event is sent. Errors raised by +the :class:`~pyramid.events.NewResponse` event is sent. Errors raised by response callbacks are not handled specially. They will be propagated to the caller of the :app:`Pyramid` router application. A response callback has a lifetime of a *single* request. If you want a response callback to happen as the result of *every* request, you must re-register the callback into every new request (perhaps within a subscriber -of a :class:`pyramid.events.NewRequest` event). +of a :class:`~pyramid.events.NewRequest` event). + +.. index:: + single: finished callback .. _using_finished_callbacks: Using Finished Callbacks ------------------------ -A :term:`finished callback` is a function that will be called -unconditionally by the :app:`Pyramid` :term:`router` at the very -end of request processing. A finished callback can be used to perform -an action at the end of a request unconditionally. +A :term:`finished callback` is a function that will be called unconditionally +by the :app:`Pyramid` :term:`router` at the very end of request processing. +A finished callback can be used to perform an action at the end of a request +unconditionally. -The :meth:`pyramid.request.Request.add_finished_callback` method is -used to register a finished callback. +The :meth:`pyramid.request.Request.add_finished_callback` method is used to +register a finished callback. A finished callback is a callable which accepts a single positional parameter: ``request``. For example: @@ -561,27 +348,27 @@ parameter: ``request``. For example: transaction.commit() request.add_finished_callback(commit_callback) -Finished callbacks are called in the order they're added ( first- to -most-recently- added). Finished callbacks (unlike a :term:`response -callback`) are *always* called, even if an exception happens in -application code that prevents a response from being generated. - -The set of finished callbacks associated with a request are called -*very late* in the processing of that request; they are essentially -the very last thing called by the :term:`router` before a request -"ends". They are called after response processing has already occurred -in a top-level ``finally:`` block within the router request processing -code. As a result, mutations performed to the ``request`` provided to -a finished callback will have no meaningful effect, because response -processing will have already occurred, and the request's scope will -expire almost immediately after all finished callbacks have been -processed. +Finished callbacks are called in the order they're added +(first-to-most-recently-added). Finished callbacks (unlike a +:term:`response callback`) are *always* called, even if an exception +happens in application code that prevents a response from being +generated. + +The set of finished callbacks associated with a request are called *very +late* in the processing of that request; they are essentially the very last +thing called by the :term:`router` before a request "ends". They are called +after response processing has already occurred in a top-level ``finally:`` +block within the router request processing code. As a result, mutations +performed to the ``request`` provided to a finished callback will have no +meaningful effect, because response processing will have already occurred, +and the request's scope will expire almost immediately after all finished +callbacks have been processed. It is often necessary to tell whether an exception occurred within -:term:`view callable` code from within a finished callback: in such a -case, the :attr:`request.exception` attribute of the request when it -enters a response callback will be an exception object instead of its -default value of ``None``. +:term:`view callable` code from within a finished callback: in such a case, +the :attr:`request.exception` attribute of the request when it enters a +response callback will be an exception object instead of its default value of +``None``. Errors raised by finished callbacks are not handled specially. They will be propagated to the caller of the :app:`Pyramid` router @@ -590,30 +377,266 @@ application. A finished callback has a lifetime of a *single* request. If you want a finished callback to happen as the result of *every* request, you must re-register the callback into every new request (perhaps within a subscriber -of a :class:`pyramid.events.NewRequest` event). +of a :class:`~pyramid.events.NewRequest` event). + +.. index:: + single: traverser + +.. _changing_the_traverser: + +Changing the Traverser +---------------------- + +The default :term:`traversal` algorithm that :app:`Pyramid` uses is explained +in :ref:`traversal_algorithm`. Though it is rarely necessary, this default +algorithm can be swapped out selectively for a different traversal pattern +via configuration. + +.. code-block:: python + :linenos: + + from pyramid.interfaces import ITraverser + from zope.interface import Interface + from myapp.traversal import Traverser + + config.registry.registerAdapter(Traverser, (Interface,), ITraverser) + +In the example above, ``myapp.traversal.Traverser`` is assumed to be a class +that implements the following interface: + +.. code-block:: python + :linenos: + + class Traverser(object): + def __init__(self, root): + """ Accept the root object returned from the root factory """ + + def __call__(self, request): + """ Return a dictionary with (at least) the keys ``root``, + ``context``, ``view_name``, ``subpath``, ``traversed``, + ``virtual_root``, and ``virtual_root_path``. These values are + typically the result of a resource tree traversal. ``root`` + is the physical root object, ``context`` will be a resource + object, ``view_name`` will be the view name used (a Unicode + name), ``subpath`` will be a sequence of Unicode names that + followed the view name but were not traversed, ``traversed`` + will be a sequence of Unicode names that were traversed + (including the virtual root path, if any) ``virtual_root`` + will be a resource object representing the virtual root (or the + physical root if traversal was not performed), and + ``virtual_root_path`` will be a sequence representing the + virtual root path (a sequence of Unicode names) or None if + traversal was not performed. + + Extra keys for special purpose functionality can be added as + necessary. + + All values returned in the dictionary will be made available + as attributes of the ``request`` object. + """ + +More than one traversal algorithm can be active at the same time. For +instance, if your :term:`root factory` returns more than one type of object +conditionally, you could claim that an alternate traverser adapter is ``for`` +only one particular class or interface. When the root factory returned an +object that implemented that class or interface, a custom traverser would be +used. Otherwise, the default traverser would be used. For example: + +.. code-block:: python + :linenos: + + from pyramid.interfaces import ITraverser + from zope.interface import Interface + from myapp.traversal import Traverser + from myapp.resources import MyRoot + + config.registry.registerAdapter(Traverser, (MyRoot,), ITraverser) + +If the above stanza was added to a Pyramid ``__init__.py`` file's ``main`` +function, :app:`Pyramid` would use the ``myapp.traversal.Traverser`` only +when the application :term:`root factory` returned an instance of the +``myapp.resources.MyRoot`` object. Otherwise it would use the default +:app:`Pyramid` traverser to do traversal. + +.. index:: + single: url generator + +.. _changing_resource_url: + +Changing How :mod:`pyramid.url.resource_url` Generates a URL +------------------------------------------------------------ + +When you add a traverser as described in :ref:`changing_the_traverser`, it's +often convenient to continue to use the :func:`pyramid.url.resource_url` API. +However, since the way traversal is done will have been modified, the URLs it +generates by default may be incorrect. + +If you've added a traverser, you can change how +:func:`~pyramid.url.resource_url` generates a URL for a specific type of +resource by adding a registerAdapter call for +:class:`pyramid.interfaces.IContextURL` to your application: + +.. code-block:: python + :linenos: + + from pyramid.interfaces import ITraverser + from zope.interface import Interface + from myapp.traversal import URLGenerator + from myapp.resources import MyRoot + + config.registry.registerAdapter(URLGenerator, (MyRoot, Interface), + IContextURL) + +In the above example, the ``myapp.traversal.URLGenerator`` class will be used +to provide services to :func:`~pyramid.url.resource_url` any time the +:term:`context` passed to ``resource_url`` is of class +``myapp.resources.MyRoot``. The second argument in the ``(MyRoot, +Interface)`` tuple represents the type of interface that must be possessed by +the :term:`request` (in this case, any interface, represented by +``zope.interface.Interface``). + +The API that must be implemented by a class that provides +:class:`~pyramid.interfaces.IContextURL` is as follows: + +.. code-block:: python + :linenos: + + from zope.interface import Interface + + class IContextURL(Interface): + """ An adapter which deals with URLs related to a context. + """ + def __init__(self, context, request): + """ Accept the context and request """ + + def virtual_root(self): + """ Return the virtual root object related to a request and the + current context""" + + def __call__(self): + """ Return a URL that points to the context """ + +The default context URL generator is available for perusal as the class +:class:`pyramid.traversal.TraversalContextURL` in the `traversal module +<http://github.com/Pylons/pyramid/blob/master/pyramid/traversal.py>`_ of the +:term:`Pylons` GitHub Pyramid repository. + +.. index:: + single: view mapper + +.. _using_a_view_mapper: + +Using a View Mapper +------------------- + +The default calling conventions for view callables are documented in the +:ref:`views_chapter` chapter. You can change the way users define view +callbles by employing a :term:`view mapper`. + +A view mapper is an object that accepts a set of keyword arguments and which +returns a callable. The returned callable is called with the :term:`view +callable` object. The returned callable should itself return another +callable which can be called with the "internal calling protocol" ``(context, +request)``. + +You can use a view mapper in a number of ways: + +- by setting a ``__view_mapper__`` attribute (which is the view mapper + object) on the view callable itself + +- by passing the mapper object to + :meth:`pyramid.config.Configurator.add_view` (or its declarative/decorator + equivalents) as the ``mapper`` argument. + +- by registering a *default* view mapper. + +Here's an example of a view mapper that emulates (somewhat) a Pylons +"controller". The mapper is initialized with some keyword arguments. Its +``__call__`` method accepts the view object (which will be a class). It uses +the ``attr`` keyword argument it is passed to determine which attribute +should be used as an action method. The wrapper method it returns accepts +``(context, request)`` and returns the result of calling the action method +with keyword arguments implied by the :term:`matchdict` after popping the +``action`` out of it. This somewhat emulates the Pylons style of calling +action methods with routing parameters pulled out of the route matching dict +as keyword arguments. + +.. code-block:: python + :linenos: + + # framework + + class PylonsControllerViewMapper(object): + def __init__(self, **kw): + self.kw = kw + + def __call__(self, view): + attr = self.kw['attr'] + def wrapper(context, request): + matchdict = request.matchdict.copy() + matchdict.pop('action', None) + inst = view() + meth = getattr(inst, attr) + return meth(**matchdict) + return wrapper + + class BaseController(object): + __view_mapper__ = PylonsControllerViewMapper + +A user might make use of these framework components like so: + +.. code-block:: python + :linenos: + + # user application + + from webob import Response + from pyramid.config import Configurator + import pyramid_handlers + from paste.httpserver import serve + + class MyController(BaseController): + def index(self, id): + return Response(id) + + if __name__ == '__main__': + config = Configurator() + config.include(pyramid_handlers) + config.add_handler('one', '/{id}', MyController, action='index') + config.add_handler('two', '/{action}/{id}', MyController) + serve(config.make_wsgi_app()) + +The :meth:`pyramid.config.Configurator.set_default_mapper` method can be used +to set a *default* view mapper (overriding the superdefault view mapper used +by Pyramid itself). + +A *single* view registration can use a view mapper by passing the mapper as +the ``mapper`` argument to :meth:`~pyramid.config.Configuration.add_view`. + +.. index:: + single: configuration decorator .. _registering_configuration_decorators: Registering Configuration Decorators ------------------------------------ -Decorators such as :class:`pyramid.view.view_config` don't change the -behavior of the functions or classes they're decorating. Instead, -when a :term:`scan` is performed, a modified version of the function -or class is registered with :app:`Pyramid`. +Decorators such as :class:`~pyramid.view.view_config` don't change the +behavior of the functions or classes they're decorating. Instead, when a +:term:`scan` is performed, a modified version of the function or class is +registered with :app:`Pyramid`. -You may wish to have your own decorators that offer such -behaviour. This is possible by using the :term:`Venusian` package in -the same way that it is used by :app:`Pyramid`. +You may wish to have your own decorators that offer such behaviour. This is +possible by using the :term:`Venusian` package in the same way that it is +used by :app:`Pyramid`. -By way of example, let's suppose you want to write a decorator that -registers the function it wraps with a :term:`Zope Component -Architecture` "utility" within the :term:`application registry` -provided by :app:`Pyramid`. The application registry and the -utility inside the registry is likely only to be available once your -application's configuration is at least partially completed. A normal -decorator would fail as it would be executed before the configuration -had even begun. +By way of example, let's suppose you want to write a decorator that registers +the function it wraps with a :term:`Zope Component Architecture` "utility" +within the :term:`application registry` provided by :app:`Pyramid`. The +application registry and the utility inside the registry is likely only to be +available once your application's configuration is at least partially +completed. A normal decorator would fail as it would be executed before the +configuration had even begun. However, using :term:`Venusian`, the decorator could be written as follows: @@ -631,7 +654,7 @@ follows: self.path = path def register(self, scanner, name, wrapped): - registry = get_current_registry() + registry = scanner.config.registry registry.getUtility(IMyUtility).register( self.path, wrapped ) @@ -671,10 +694,8 @@ performed, enabling you to set up the utility in advance: if __name__ == '__main__': config = Configurator() - config.begin() config.registry.registerUtility(UtilityImplementation()) config.scan() - config.end() app = config.make_wsgi_app() serve(app, host='0.0.0.0') diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index 181ae1285..780cb0975 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -156,12 +156,12 @@ match is straightforward. When a route is matched: - If the route's configuration does not have a ``factory`` argument, the *global* :term:`root factory` will be called to generate a :term:`root` object. The global root factory is the - callable implied by the ``root_factory`` argument passed to - :class:`pyramid.config.Configurator` at application + callable implied by the ``root_factory`` argument passed to the + :class:`~pyramid.config.Configurator` at application startup time. - If a ``root_factory`` argument is not provided to the - :class:`pyramid.config.Configurator` at startup time, a + :class:`~pyramid.config.Configurator` at startup time, a *default* root factory is used. The default root factory is used to generate a root object. @@ -260,7 +260,7 @@ route statement is a reasonable thing to do. We could have also used our ``root_factory`` callable as the ``root_factory`` argument of the - :class:`pyramid.config.Configurator` constructor, instead + :class:`~pyramid.config.Configurator` constructor, instead of associating it with a particular route inside the route's configuration. Every hybrid route configuration that is matched but which does *not* name a ``factory`` attribute will use the use @@ -300,13 +300,13 @@ invoked after a route matches: config.add_view('mypackage.views.myview', route_name='home') Note that the above call to -:meth:`pyramid.config.Configurator.add_view` includes a ``route_name`` +:meth:`~pyramid.config.Configurator.add_view` includes a ``route_name`` argument. View configurations that include a ``route_name`` argument are meant to associate a particular view declaration with a route, using the route's name, in order to indicate that the view should *only be invoked when the route matches*. -Calls to :meth:`pyramid.config.Configurator.add_view` may pass a +Calls to :meth:`~pyramid.config.Configurator.add_view` may pass a ``route_name`` attribute, which refers to the value of an existing route's ``name`` argument. In the above example, the route name is ``home``, referring to the name of the route defined above it. @@ -357,7 +357,7 @@ Using the ``traverse`` Argument In a Route Definition Rather than using the ``*traverse`` remainder marker in a pattern, you can use the ``traverse`` argument to the -:meth:`pyramid.config.Configurator.add_route` method. +:meth:`~pyramid.config.Configurator.add_route` method. When you use the ``*traverse`` remainder marker, the traversal path is limited to being the remainder segments of a request URL when a route @@ -365,7 +365,7 @@ matches. However, when you use the ``traverse`` argument or attribute, you have more control over how to compose a traversal path. Here's a use of the ``traverse`` pattern in a call to -:meth:`pyramid.config.Configurator.add_route`: +:meth:`~pyramid.config.Configurator.add_route`: .. code-block:: python :linenos: @@ -472,7 +472,7 @@ startup time. config.add_view('myproject.views.another', route_name='home') This is because the ``view`` argument to the -:meth:`pyramid.config.Configurator.add_route` above is an *implicit* +:meth:`~pyramid.config.Configurator.add_route` above is an *implicit* default view when that route matches. ``add_route`` calls don't *need* to supply a view attribute. For example, this ``add_route`` call: diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index d8cc5cb1c..218b7a2b4 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -59,7 +59,7 @@ This creates a Unicode-like object that is a TranslationString. :term:`Django` i18n, using a TranslationString is a lot like using "lazy" versions of related gettext APIs. -The first argument to :class:`pyramid.i18n.TranslationString` is +The first argument to :class:`~pyramid.i18n.TranslationString` is the ``msgid``; it is required. It represents the key into the translation mappings provided by a particular localization. The ``msgid`` argument must be a Unicode object or an ASCII string. The @@ -139,7 +139,7 @@ Using the ``TranslationStringFactory`` Class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Another way to generate a translation string is to use the -:attr:`pyramid.i18n.TranslationStringFactory` object. This object +:attr:`~pyramid.i18n.TranslationStringFactory` object. This object is a *translation string factory*. Basically a translation string factory presets the ``domain`` value of any :term:`translation string` generated by using it. For example: @@ -156,11 +156,11 @@ generated by using it. For example: file generation tools. After assigning ``_`` to the result of a -:func:`pyramid.i18n.TranslationStringFactory`, the subsequent result -of calling ``_`` will be a :class:`pyramid.i18n.TranslationString` +:func:`~pyramid.i18n.TranslationStringFactory`, the subsequent result +of calling ``_`` will be a :class:`~pyramid.i18n.TranslationString` instance. Even though a ``domain`` value was not passed to ``_`` (as would have been necessary if the -:class:`pyramid.i18n.TranslationString` constructor were used instead +:class:`~pyramid.i18n.TranslationString` constructor were used instead of a translation string factory), the ``domain`` attribute of the resulting translation string will be ``pyramid``. As a result, the previous code example is completely equivalent (except for spelling) @@ -175,7 +175,7 @@ to: You can set up your own translation string factory much like the one provided above by using the -:class:`pyramid.i18n.TranslationStringFactory` class. For example, +:class:`~pyramid.i18n.TranslationStringFactory` class. For example, if you'd like to create a translation string factory which presets the ``domain`` value of generated translation strings to ``form``, you'd do something like this: @@ -344,7 +344,8 @@ which reside in your :app:`Pyramid` application. You run a $ mkdir -p myapplication/locale $ python setup.py extract_messages -The message catalog ``.pot`` template will end up in +The message catalog ``.pot`` template will end up in: + ``myapplication/locale/myapplication.pot``. Translation Domains @@ -432,7 +433,8 @@ init_catalog`` command: $ cd /place/where/myapplication/setup.py/lives $ python setup.py init_catalog -l es -By default, the message catalog ``.po`` file will end up in +By default, the message catalog ``.po`` file will end up in: + ``myapplication/locale/es/LC_MESSAGES/myapplication.po``. Once the file is there, it can be worked on by a human translator. @@ -491,13 +493,12 @@ translations will be available to :app:`Pyramid`. Using a Localizer ----------------- -A :term:`localizer` is an object that allows you to perform -translation or pluralization "by hand" in an application. You may use -the :func:`pyramid.i18n.get_localizer` function to obtain a -:term:`localizer`. :func:`pyramid.i18n.get_localizer`. This -function will return either the localizer object implied by the active -:term:`locale negotiator` or a default localizer object if no explicit -locale negotiator is registered. +A :term:`localizer` is an object that allows you to perform translation or +pluralization "by hand" in an application. You may use the +:func:`pyramid.i18n.get_localizer` function to obtain a :term:`localizer`. +This function will return either the localizer object implied by the active +:term:`locale negotiator` or a default localizer object if no explicit locale +negotiator is registered. .. code-block:: python :linenos: @@ -507,6 +508,9 @@ locale negotiator is registered. def aview(request): locale = get_localizer(request) +.. note:: If you need to create a localizer for a locale use the + :func:`pyramid.i18n.make_localizer` function. + .. index:: single: translating (i18n) @@ -534,7 +538,7 @@ translation in a view component of an application might look like so: translated = localizer.translate(ts) # translation string # ... use translated ... -The :func:`pyramid.i18n.get_localizer` function will return a +The :func:`~pyramid.i18n.get_localizer` function will return a :class:`pyramid.i18n.Localizer` object bound to the locale name represented by the request. The translation returned from its :meth:`pyramid.i18n.Localizer.translate` method will depend on the @@ -612,9 +616,9 @@ locale negotiator returns ``None``. You can change the default locale name by changing the ``default_locale_name`` setting; see :ref:`default_locale_name_setting`. -Once :func:`pyramid.i18n.get_locale_name` is first run, the locale +Once :func:`~pyramid.i18n.get_locale_name` is first run, the locale name is stored on the request object. Subsequent calls to -:func:`pyramid.i18n.get_locale_name` will return the stored locale +:func:`~pyramid.i18n.get_locale_name` will return the stored locale name without invoking the :term:`locale negotiator`. To avoid this caching, you can use the :func:`pyramid.i18n.negotiate_locale_name` function: @@ -641,7 +645,7 @@ You can also obtain the locale name related to a request using the Obtaining the locale name as an attribute of a localizer is equivalent to obtaining a locale name by calling the -:func:`pyramid.i18n.get_locale_name` function. +:func:`~pyramid.i18n.get_locale_name` function. .. index:: single: date and currency formatting (i18n) @@ -735,6 +739,13 @@ this support out of the box and may need special code to do an equivalent. For those, you can always use the more manual translation facility described in :ref:`performing_a_translation`. +Mako Pyramid I18N Support +------------------------- + +There exists a recipe within the :term:`Pyramid Cookbook` named "Mako +Internationalization" which explains how to add idiomatic I18N support to +:term:`Mako` templates. + .. index:: single: localization deployment settings single: default_locale_name @@ -747,7 +758,7 @@ Localization-Related Deployment Settings A :app:`Pyramid` application will have a ``default_locale_name`` setting. This value represents the :term:`default locale name` used when the :term:`locale negotiator` returns ``None``. Pass it to the -:mod:`pyramid.config.Configurator` constructor at startup +:mod:`~pyramid.config.Configurator` constructor at startup time: .. code-block:: python @@ -867,11 +878,11 @@ which itself includes an ``LC_MESSAGES`` directory. Each Each ``.mo`` file represents a :term:`message catalog`, which is used to provide translations to your application. -Adding a :term:`translation directory` registers all of its -constituent :term:`message catalog` files (all of the ``.mo`` files -found within all ``LC_MESSAGES`` directories within each locale -directory in the translation directory) within your :app:`Pyramid` -application to be available to use for translation services. +Adding a :term:`translation directory` registers all of its constituent +:term:`message catalog` files within your :app:`Pyramid` application to +be available to use for translation services. This includes all of the +``.mo`` files found within all ``LC_MESSAGES`` directories within each +locale directory in the translation directory. You can add a translation directory imperatively by using the :meth:`pyramid.config.Configurator.add_translation_dirs` during @@ -881,21 +892,15 @@ application startup. For example: :linenos: from pyramid.config import Configurator - config.begin() config.add_translation_dirs('my.application:locale/', 'another.application:locale/') - # ... - config.end() A message catalog in a translation directory added via -:meth:`pyramid.config.Configurator.add_translation_dirs` +:meth:`~pyramid.config.Configurator.add_translation_dirs` will be merged into translations from a message catalog added earlier if both translation directories contain translations for the same locale and :term:`translation domain`. -.. note:: You can also add a translation directory via ZCML. See - :ref:`zcml_adding_a_translation_directory` - Setting the Locale ~~~~~~~~~~~~~~~~~~ @@ -937,8 +942,8 @@ a particular request. A locale negotiator is a bit of code which accepts a request and which returns a :term:`locale name`. It is consulted when :meth:`pyramid.i18n.Localizer.translate` or :meth:`pyramid.i18n.Localizer.pluralize` is invoked. It is also -consulted when :func:`pyramid.i18n.get_locale_name` or -:func:`pyramid.i18n.negotiate_locale_name` is invoked. +consulted when :func:`~pyramid.i18n.get_locale_name` or +:func:`~pyramid.i18n.negotiate_locale_name` is invoked. .. _default_locale_negotiator: @@ -949,7 +954,7 @@ Most applications can make use of the default locale negotiator, which requires no additional coding or configuration. The default locale negotiator implementation named -:class:`pyramid.i18n.default_locale_negotiator` uses the following +:class:`~pyramid.i18n.default_locale_negotiator` uses the following set of steps to dermine the locale name. - First, the negotiator looks for the ``_LOCALE_`` attribute of the @@ -1000,7 +1005,7 @@ You may add your newly created locale negotiator to your application's configuration by passing an object which can act as the negotiator (or a :term:`dotted Python name` referring to the object) as the ``locale_negotiator`` argument of the -:class:`pyramid.config.Configurator` instance during application +:class:`~pyramid.config.Configurator` instance during application startup. For example: .. code-block:: python @@ -1020,9 +1025,5 @@ For example: from pyramid.config import Configurator config = Configurator() - config.begin() config.set_locale_negotiator(my_locale_negotiator) - config.end() -.. note:: You can also add a custom locale negotiator via ZCML. See - :ref:`zcml_adding_a_locale_negotiator` diff --git a/docs/narr/install.rst b/docs/narr/install.rst index e32d0c6c3..c5ec14aa1 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -295,7 +295,7 @@ Installing :app:`Pyramid` on a Windows System c:\> cd env -#. (Optional) Consider using ``bin\activate.bat`` to make your shell +#. (Optional) Consider using ``Scripts\activate.bat`` to make your shell environment wired to use the virtualenv. #. Use ``easy_install`` pointed at the "current" index to get diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 3ade3726c..a0b682e25 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -9,23 +9,12 @@ :app:`Pyramid` Introduction ============================== -If they are judged only by differences in user interface, most web -applications seem to have very little in common with each other. For -example, a web page served by one web application might be a -representation of the contents of an accounting ledger, while a web -page served by another application might be a listing of songs. These -applications probably won't serve the same set of customers. However, -although they're not very similar on the surface, both a -ledger-serving application and a song-serving application can be -written using :app:`Pyramid`. - -:app:`Pyramid` is a very general open source Python web -*framework*. As a framework, its primary job is to make it easier for -a developer to create an arbitrary web application. The type of -application being created isn't really important; it could be a -spreadsheet, a corporate intranet, or an "oh-so-Web-2.0" social -networking platform. :app:`Pyramid` is general enough that it can -be used in a wide variety of circumstances. +:app:`Pyramid` is a general, open source, Python web application development +*framework*. Its primary goal is to make it easier for a developer to create +web applications. The type of application being created could be a +spreadsheet, a corporate intranet, or a social networking platform; Pyramid's +generality enables it to be used to build an unconstrained variety of web +applications. .. sidebar:: Frameworks vs. Libraries @@ -44,55 +33,45 @@ be used in a wide variety of circumstances. own via a set of libraries if the framework provides a set of facilities that fits your application requirements. -The first release of the predecessor to :app:`Pyramid` (named -:mod:`repoze.bfg`) was made in July of 2008. Since its first release, -we've tried to ensure that it maintains the following attributes: +The first release of Pyramid's predecessor (named :mod:`repoze.bfg`) was made +in July of 2008. We have worked hard to ensure that Pyramid continues to +follow the design and engineering principles that we consider to be the core +characteristics of a successful framework: Simplicity - :app:`Pyramid` attempts to be a *"pay only for what you eat"* - framework which delivers results even if you have only partial - knowledge. Other frameworks may expect you to understand many - concepts and technologies fully before you can be truly productive. - :app:`Pyramid` doesn't force you to use any particular technology - to produce an application, and we try to keep the core set of - concepts you need to understand to a minimum. - -A Sense of Fun - Developing a :app:`Pyramid` application should not feel - "enterprisey". We like to keep things down-to-earth. + :app:`Pyramid` takes a *"pay only for what you eat"* approach. This means + that you can get results even if you have only a partial understanding of + :app:`Pyramid`. It doesn’t force you to use any particular technology to + produce an application, and we try to keep the core set of concepts that + you need to understand to a minimum. Minimalism - :app:`Pyramid` provides only the very basics: *URL to code - mapping*, *templating*, *security*, and *resources*. There is not - much more to the framework than these pieces: you are expected to - provide the rest. + :app:`Pyramid` concentrates on providing fast, high-quality solutions to + the fundamental problems of creating a web application: the mapping of URLs + to code, templating, security and serving static assets. We consider these + to be the core activities that are common to nearly all web applications. Documentation - Because :app:`Pyramid` is minimal, it's relatively easy to keep - its documentation up-to-date, which is helpful to bring new - developers up to speed. It's our goal that nothing remain - undocumented about :app:`Pyramid`. + Pyramid's minimalism means that it is relatively easy for us to maintain + extensive and up-to-date documentation. It is our goal that no aspect of + Pyramid remains undocumented. Speed - :app:`Pyramid` is faster than many other popular Python web - frameworks for common tasks such as templating and simple response - generation. The "hardware is cheap" mantra has its limits when - you're responsible for managing a great many machines: the fewer you - need, the less pain you'll have. - -Familiarity - The :app:`Pyramid` framework is a canonization of practices that - "fit the brains" of its authors. - -Trustability - :app:`Pyramid` is developed conservatively and tested - exhaustively. *If it ain't tested, it's broke.* Every release of - :app:`Pyramid` has 100% statement coverage via unit tests. + :app:`Pyramid` is designed to provide noticeably fast execution for common + tasks such as templating and simple response generation. Although the + “hardware is cheap” mantra may appear to offer a ready solution to speed + problems, the limits of this approach become painfully evident when one + finds him or herself responsible for managing a great many machines. + +Reliability + :app:`Pyramid` is developed conservatively and tested exhaustively. Where + Pyramid source code is concerned, our motto is: "If it ain’t tested, it’s + broke". Every release of Pyramid has 100% statement coverage via unit + tests. Openness - Like :term:`Python`, the :app:`Pyramid` software is distributed - under a `permissive open source license - <http://repoze.org/license.html>`_. + As with Python, the Pyramid software is distributed under a `permissive + open source license <http://repoze.org/license.html>`_. .. index:: single: Pylons @@ -104,7 +83,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://docs.pylonshq.com>`_ +contributors. The `Pylons Project website <http://docs.pylonsproject.org>`_ includes details about how :app:`Pyramid` relates to the Pylons Project. .. index:: diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst new file mode 100644 index 000000000..a4709ef18 --- /dev/null +++ b/docs/narr/muchadoabouttraversal.rst @@ -0,0 +1,309 @@ +.. _much_ado_about_traversal_chapter: + +======================== +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/ . + +Traversal is an alternative to :term:`URL dispatch` which allows +:app:`Pyramid` applications to map URLs to code. + +.. note:: + + Ex-Zope users whom are already familiar with traversal and view lookup + conceptually may want to skip directly to the :ref:`traversal_chapter` + chapter, which discusses technical details. This chapter is mostly aimed + at people who have previous :term:`Pylons` experience or experience in + another framework which does not provide traversal, and need an + introduction to the "why" of traversal. + +Some folks who have been using Pylons and its Routes-based URL matching for a +long time are being exposed for the first time, via :app:`Pyramid`, to new +ideas such as ":term:`traversal`" and ":term:`view lookup`" as a way to route +incoming HTTP requests to callable code. Some of the same folks believe that +traversal is hard to understand. Others question its usefulness; URL +matching has worked for them so far, why should they even consider dealing +with another approach, one which doesn't fit their brain and which doesn't +provide any immediately obvious value? + +You can be assured that if you don't want to understand traversal, you don't +have to. You can happily build :app:`Pyramid` applications with only +:term:`URL dispatch`. However, there are some straightforward, real-world +use cases that are much more easily served by a traversal-based approach than +by a pattern-matching mechanism. Even if you haven't yet hit one of these +use cases yourself, understanding these new ideas is worth the effort for any +web developer so you know when you might want to use them. :term:`Traversal` +is actually a straightforward metaphor easily comprehended by anyone who's +ever used a run-of-the-mill file system with folders and files. + +URL Dispatch +------------ + +Let's step back and consider the problem we're trying to solve. An +HTTP request for a particular path has been routed to our web +application. The requested path will possibly invoke a specific +:term:`view callable` function defined somewhere in our app. We're +trying to determine *which* callable function, if any, should be +invoked for a given requested URL. + +Many systems, including Pyramid, offer a simple solution. They offer the +concept of "URL matching". URL matching approaches this problem by parsing +the URL path and comparing the results to a set of registered "patterns", +defined by a set of regular expressions, or some other URL path templating +syntax. Each pattern is mapped to a callable function somewhere; if the +request path matches a specific pattern, the associated function is called. +If the request path matches more than one pattern, some conflict resolution +scheme is used, usually a simple order precedence so that the first match +will take priority over any subsequent matches. If a request path doesn't +match any of the defined patterns, a "404 Not Found" response is returned. + +In Pyramid, we offer an implementation of URL matching which we call +:term:`URL dispatch`. Using :app:`Pyramid` syntax, we might have a match +pattern such as ``/{userid}/photos/{photoid}``, mapped to a ``photo_view()`` +function defined somewhere in our code. Then a request for a path such as +``/joeschmoe/photos/photo1`` would be a match, and the ``photo_view()`` +function would be invoked to handle the request. Similarly, +``/{userid}/blog/{year}/{month}/{postid}`` might map to a +``blog_post_view()`` function, so ``/joeschmoe/blog/2010/12/urlmatching`` +would trigger the function, which presumably would know how to find and +render the ``urlmatching`` blog post. + +Historical Refresher +-------------------- + +Now that we've refreshed our understanding of :term:`URL dispatch`, we'll dig +in to the idea of traversal. Before we do, though, let's take a trip down +memory lane. If you've been doing web work for a while, you may remember a +time when we didn't have fancy web frameworks like :term:`Pylons` and +:app:`Pyramid`. Instead, we had general purpose HTTP servers that primarily +served files off of a file system. The "root" of a given site mapped to a +particular folder somewhere on the file system. Each segment of the request +URL path represented a subdirectory. The final path segment would be either +a directory or a file, and once the server found the right file it would +package it up in an HTTP response and send it back to the client. So serving +up a request for ``/joeschmoe/photos/photo1`` literally meant that there was +a ``joeschmoe`` folder somewhere, which contained a ``photos`` folder, which +in turn contained a ``photo1`` file. If at any point along the way we find +that there is not a folder or file matching the requested path, we return a +404 response. + +As the web grew more dynamic, however, a little bit of extra complexity was +added. Technologies such as CGI and HTTP server modules were developed. +Files were still looked up on the file system, but if the file ended with +(for example) ``.cgi`` or ``.php``, or if it lived in a special folder, +instead of simply sending the file to the client the server would read the +file, execute it using an interpreter of some sort, and then send the output +from this process to the client as the final result. The server +configuration specified which files would trigger some dynamic code, with the +default case being to just serve the static file. + +Traversal (aka Resource Location) +--------------------------------- + +.. index:: + single: traversal overview + +Believe it or not, if you understand how serving files from a file system +works, you understand traversal. And if you understand that a server might do +something different based on what type of file a given request specifies, +then you understand view lookup. + +The major difference between file system lookup and traversal is that a file +system lookup steps through nested directories and files in a file system +tree, while traversal steps through nested dictionary-type objects in a +:term:`resource tree`. Let's take a detailed look at one of our example +paths, so we can see what I mean: + +The path ``/joeschmoe/photos/photo1``, has four segments: ``/``, +``joeschmoe``, ``photos`` and ``photo1``. With file system lookup we might +have a root folder (``/``) containing a nested folder (``joeschmoe``), which +contains another nested folder (``photos``), which finally contains a JPG +file (``photo1``). With traversal, we instead have a dictionary-like root +object. Asking for the ``joeschmoe`` key gives us another dictionary-like +object. Asking this in turn for the ``photos`` key gives us yet another +mapping object, which finally (hopefully) contains the resource that we're +looking for within its values, referenced by the ``photo1`` key. + +In pure Python terms, then, the traversal or "resource location" +portion of satisfying the ``/joeschmoe/photos/photo1`` request +will look something like this pseudocode:: + + get_root()['joeschmoe']['photos']['photo1'] + +``get_root()`` is some function that returns a root traversal +:term:`resource`. If all of the specified keys exist, then the returned +object will be the resource that is being requested, analogous to the JPG +file that was retrieved in the file system example. If a :exc:`KeyError` is +generated anywhere along the way, :app:`Pyramid` will return 404. (This +isn't precisely true, as you'll see when we learn about view lookup below, +but the basic idea holds.) + +What Is a "Resource"? +--------------------- + +"Files on a file system I understand", you might say. "But what are these +nested dictionary things? Where do these objects, these 'resources', live? +What *are* they?" + +Since :app:`Pyramid` is not a highly opinionated framework, it makes no +restriction on how a :term:`resource` is implemented; a developer can +implement them as he wishes. One common pattern used is to persist all of +the resources, including the root, in a database as a graph. The root object +is a dictionary-like object. Dictionary-like objects in Python supply a +``__getitem__`` method which is called when key lookup is done. Under the +hood, when ``adict`` is a dictionary-like object, Python translates +``adict['a']`` to ``adict.__getitem__('a')``. Try doing this in a Python +interpreter prompt if you don't believe us: + +.. code-block:: text + :linenos: + + Python 2.4.6 (#2, Apr 29 2010, 00:31:48) + [GCC 4.4.3] on linux2 + Type "help", "copyright", "credits" or "license" for more information. + >>> adict = {} + >>> adict['a'] = 1 + >>> adict['a'] + 1 + >>> adict.__getitem__('a') + 1 + + +The dictionary-like root object stores the ids of all of its subresources as +keys, and provides a ``__getitem__`` implementation that fetches them. So +``get_root()`` fetches the unique root object, while +``get_root()['joeschmoe']`` returns a different object, also stored in the +database, which in turn has its own subresources and ``__getitem__`` +implementation, etc. These resources might be persisted in a relational +database, one of the many "NoSQL" solutions that are becoming popular these +days, or anywhere else, it doesn't matter. As long as the returned objects +provide the dictionary-like API (i.e. as long as they have an appropriately +implemented ``__getitem__`` method) then traversal will work. + +In fact, you don't need a "database" at all. You could use plain +dictionaries, with your site's URL structure hard-coded directly in +the Python source. Or you could trivially implement a set of objects +with ``__getitem__`` methods that search for files in specific +directories, and thus precisely recreate the traditional mechanism of +having the URL path mapped directly to a folder structure on the file +system. Traversal is in fact a superset of file system lookup. + +.. note:: See the chapter entitled :ref:`resources_chapter` for a more + technical overview of resources. + +View Lookup +----------- + +At this point we're nearly there. We've covered traversal, which is the +process by which a specific resource is retrieved according to a specific URL +path. But what is "view lookup"? + +The need for view lookup is simple: there is more than one possible action +that you might want to take after finding a :term:`resource`. With our photo +example, for instance, you might want to view the photo in a page, but you +might also want to provide a way for the user to edit the photo and any +associated metadata. We'll call the former the ``view`` view, and the latter +will be the ``edit`` view. (Original, I know.) :app:`Pyramid` has a +centralized view :term:`application registry` where named views can be +associated with specific resource types. So in our example, we'll assume +that we've registered ``view`` and ``edit`` views for photo objects, and that +we've specified the ``view`` view as the default, so that +``/joeschmoe/photos/photo1/view`` and ``/joeschmoe/photos/photo1`` are +equivalent. The edit view would sensibly be provided by a request for +``/joeschmoe/photos/photo1/edit``. + +Hopefully it's clear that the first portion of the edit view's URL path is +going to resolve to the same resource as the non-edit version, specifically +the resource returned by ``get_root()['joeschmoe']['photos']['photo1']``. +But traveral ends there; the ``photo1`` resource doesn't have an ``edit`` +key. In fact, it might not even be a dictionary-like object, in which case +``photo1['edit']`` would be meaningless. When the :app:`Pyramid` resource +location has been resolved to a *leaf* resource, but the entire request path +has not yet been expended, the *very next* path segment is treated as a +:term:`view name`. The registry is then checked to see if a view of the +given name has been specified for a resource of the given type. If so, the +view callable is invoked, with the resource passed in as the related +``context`` object (also available as ``request.context``). If a view +callable could not be found, :app:`Pyramid` will return a "404 Not Found" +response. + +You might conceptualize a request for ``/joeschmoe/photos/photo1/edit`` as +ultimately converted into the following piece of Pythonic pseudocode:: + + context = get_root()['joeschmoe']['photos']['photo1'] + view_callable = get_view(context, 'edit') + request.context = context + view_callable(request) + +The ``get_root`` and ``get_view`` functions don't really exist. Internally, +:app:`Pyramid` does something more complicated. But the example above +is a reasonable approximation of the view lookup algorithm in pseudocode. + +Use Cases +--------- + +Why should we care about traversal? URL matching is easier to explain, and +it's good enough, right? + +In some cases, yes, but certainly not in all cases. So far we've had very +structured URLs, where our paths have had a specific, small number of pieces, +like this:: + + /{userid}/{typename}/{objectid}[/{view_name}] + +In all of the examples thus far, we've hard coded the typename value, +assuming that we'd know at development time what names were going to be used +("photos", "blog", etc.). But what if we don't know what these names will +be? Or, worse yet, what if we don't know *anything* about the structure of +the URLs inside a user's folder? We could be writing a CMS where we want the +end user to be able to arbitrarily add content and other folders inside his +folder. He might decide to nest folders dozens of layers deep. How will you +construct matching patterns that could account for every possible combination +of paths that might develop? + +It might be possible, but it certainly won't be easy. The matching +patterns are going to become complex quickly as you try to handle all +of the edge cases. + +With traversal, however, it's straightforward. Twenty layers of nesting +would be no problem. :app:`Pyramid` will happily call ``__getitem__`` as +many times as it needs to, until it runs out of path segments or until a +resource raises a :exc:`KeyError`. Each resource only needs to know how to +fetch its immediate children, the traversal algorithm takes care of the rest. +Also, since the structure of the resource tree can live in the database and +not in the code, it's simple to let users modify the tree at runtime to set +up their own personalized "directory" structures. + +Another use case in which traversal shines is when there is a need to support +a context-dependent security policy. One example might be a document +management infrastructure for a large corporation, where members of different +departments have varying access levels to the various other departments' +files. Reasonably, even specific files might need to be made available to +specific individuals. Traversal does well here if your resources actually +represent the data objects related to your documents, because the idea of a +resource authorization is baked right into the code resolution and calling +process. Resource objects can store ACLs, which can be inherited and/or +overridden by the subresources. + +If each resource can thus generate a context-based ACL, then whenever view +code is attempting to perform a sensitive action, it can check against that +ACL to see whether the current user should be allowed to perform the action. +In this way you achieve so called "instance based" or "row level" security +which is considerably harder to model using a traditional tabular approach. +:app:`Pyramid` actively supports such a scheme, and in fact if you register +your views with guard permissions and use an authorization policy, +:app:`Pyramid` can check against a resource's ACL when deciding whether or +not the view itself is available to the current user. + +In summary, there are entire classes of problems that are more easily served +by traversal and view lookup than by :term:`URL dispatch`. If your problems +don't require it, great: stick with :term:`URL dispatch`. But if you're +using :app:`Pyramid` and you ever find that you *do* need to support one of +these use cases, you'll be glad you have traversal in your toolkit. + +.. note:: It is even possible to mix and match :term:`traversal` with + :term:`URL dispatch` in the same :app:`Pyramid` application. See the + :ref:`hybrid_chapter` chapter for details. diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 36f2d6975..c3a31dcbd 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -8,13 +8,13 @@ As we saw in :ref:`firstapp_chapter`, it's possible to create a convenient to use a *template* to generate a basic :app:`Pyramid` :term:`project`. -A project is a directory that contains at least one :term:`package`. You'll -use a template to create a project, and you'll create your application logic -within a package that lives inside the project. Even if your application is -extremely simple, it is useful to place code that drives the application -within a package, because a package is more easily extended with new code. -An application that lives inside a package can also be distributed more -easily than one which does not live within a package. +A project is a directory that contains at least one Python :term:`package`. +You'll use a template to create a project, and you'll create your application +logic within a package that lives inside the project. Even if your +application is extremely simple, it is useful to place code that drives the +application within a package, because a package is more easily extended with +new code. An application that lives inside a package can also be distributed +more easily than one which does not live within a package. :app:`Pyramid` comes with a variety of templates that you can use to generate a project. Each template makes different configuration assumptions about @@ -26,13 +26,9 @@ and so therefore they are often referred to as "paster templates". .. index:: single: paster templates single: pyramid_starter paster template - single: pyramid_starter_zcml paster template single: pyramid_zodb paster template single: pyramid_alchemy paster template single: pyramid_routesalchemy paster template - single: pylons_minimal paster template - single: pylons_basic paster template - single: pylons_sqla paster template .. _additional_paster_templates: @@ -48,8 +44,6 @@ each other on a number of axes: - the mechanism they use to map URLs to code (:term:`traversal` or :term:`URL dispatch`). -- the type of configuration used (:term:`ZCML` vs. imperative configuration). - - whether or not the ``pyramid_beaker`` library is relied upon as the sessioning implementation (as opposed to no sessioning or default sessioning). @@ -59,10 +53,6 @@ The included templates are these: ``pyramid_starter`` URL mapping via :term:`traversal` and no persistence mechanism. -``pyramid_starter_zcml`` - URL mapping via :term:`traversal` and no persistence mechanism, using - :term:`ZCML` (declarative configuration). - ``pyramid_zodb`` URL mapping via :term:`traversal` and persistence via :term:`ZODB`. @@ -74,19 +64,22 @@ The included templates are these: URL mapping via :term:`traversal` and persistence via :term:`SQLAlchemy` -``pylons_minimal`` - URL mapping via :term:`URL dispatch` and Pylons-style view handlers, - minimal setup, uses ``pyramid_beaker`` as a sessioning implementation. - -``pylons_basic`` - URL mapping via :term:`URL dispatch` and Pylons-style view handlers, and - some extra functionality, uses ``pyramid_beaker`` as a sessioning - implementation. - -``pylons_sqla`` - URL mapping via :term:`URL dispatch` and Pylons-style view handlers, some - extra functionality, and SQLAlchemy set up, uses ``pyramid_beaker`` as a - sessioning implementation. +.. note:: At this time, each of these templates uses the :term:`Chameleon` + templating system, which is incompatible with both Jython and PyPy. To + use paster templates to build applications which will run on Jython and + PyPy, you can try the ``pyramid_jinja2_starter`` template which ships as + part of the :term:`pyramid_jinja2` package or the ``pyramid_sqla`` paster + template which ships with the :term:`pyramid_sqla` package (it uses Mako), + both available from :term:`PyPI`. You can also just use the above paster + templates to build a skeleton and replace the Chameleon template it + includes with a :term:`Mako` analogue. + +Rather than use any of the above templates, Pylons 1 users may feel more +comfortable installing the :term:`pyramid_sqla` add-on package, which +provides a paster template named ``pyramid_sqla``. This paster template +configures a Pyramid application in a "Pylons-esque" way, including the use +of a :term:`view handler` to map URLs to code (it's much like a Pylons +"controller"). .. index:: single: creating a project @@ -144,26 +137,29 @@ project we name ``MyProject``: name during ``paster create`` by adding the project name to the command line, e.g. ``paster create -t pyramid_starter MyProject``. -.. note:: You may encounter an error when using ``paster create`` - if a dependent Python package is not installed. This will - result in a traceback ending in: - - .. code-block:: text - - pkg_resources.DistributionNotFound: <package name> - - Simply run ``bin/easy_install``, with the missing package - name from the error message, to work around this issue. +.. note:: You may encounter an error when using ``paster create`` if a + dependent Python package is not installed. This will result in a traceback + ending in ``pkg_resources.DistributionNotFound: <package name>``. + Simply run ``bin/easy_install``, with the missing package name from the + error message to work around this issue. As a result of invoking the ``paster create`` command, a project is created in a directory named ``MyProject``. 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 :term:`PasteDeploy` ``.ini`` file named ``development.ini`` will also be -created in the project directory. You will use this ``.ini`` file to -configure a server, to run your application, and to and debug your -application. +A :term:`PasteDeploy` ``.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 sports +configuration that enables an interactive debugger and settings optimized for +development. + +Another :term:`PasteDeploy` ``.ini`` file named ``production.ini`` will also +be created in the project directory. It sports configuration that disables +any interactive debugger (to prevent inappropriate access and disclosure), +and turns off a number of debugging settings. You can use this file to put +your application into production, and you can modify it to do things like +send email when an exception occurs. The ``MyProject`` project directory contains an additional subdirectory named ``myproject`` (note the case difference) representing a Python @@ -180,7 +176,7 @@ 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.py`` +command ``python setup.py develop`` The file named ``setup.py`` will be in the root of the paster-generated project directory. The ``python`` you're invoking should be the one that @@ -256,6 +252,8 @@ create`` -generated project. Within a project generated by the single: IPython single: paster pshell +.. _interactive_shell: + The Interactive Shell --------------------- @@ -322,37 +320,34 @@ unconditionally. [chrism@vitaminf shellenv]$ ../bin/paster pshell --disable-ipython \ development.ini MyProject -.. warning:: +You should always use a section name argument that refers to the actual +``app`` section within the Paste configuration file that points at your +:app:`Pyramid` application *without any middleware wrapping*. In particular, +a section name is inappropriate as the second argument to ``pshell`` if the +configuration section it names is a ``pipeline`` rather than an ``app``. For +example, if you have the following ``.ini`` file content: + +.. code-block:: ini + :linenos: + + [app:MyProject] + use = egg:MyProject + reload_templates = true + debug_authorization = false + debug_notfound = false + debug_templates = true + default_locale_name = en - You should always use a section name argument that refers to the actual - ``app`` section within the Paste configuration file that points at your - :app:`Pyramid` application *without any middleware wrapping*. In - particular, a section name is inappropriate as the second argument to - ``pshell`` if the configuration section it names is a ``pipeline`` rather - than an ``app``. For example, if you have the following ``.ini`` file - content: - - .. code-block:: guess - :linenos: - - [app:MyProject] - use = egg:MyProject - reload_templates = true - debug_authorization = false - debug_notfound = false - debug_templates = true - default_locale_name = en - - [pipeline:main] - pipeline = - egg:WebError#evalerror - MyProject - - Use ``MyProject`` instead of ``main`` as the section name argument to - ``pshell`` against the above ``.ini`` file (e.g. ``paster pshell - development.ini MyProject``). If you use ``main`` instead, an error will - occur. Use the most specific reference to your application within the - ``.ini`` file possible as the section name argument. + [pipeline:main] + pipeline = + egg:WebError#evalerror + MyProject + +Use ``MyProject`` instead of ``main`` as the section name argument to +``pshell`` against the above ``.ini`` file (e.g. ``paster pshell +development.ini MyProject``). If you use ``main`` instead, an error will +occur. Use the most specific reference to your application within the +``.ini`` file possible as the section name argument. Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). @@ -467,6 +462,7 @@ structure: MyProject/ |-- CHANGES.txt |-- development.ini + |-- MANIFEST.in |-- myproject | |-- __init__.py | |-- resources.py @@ -478,6 +474,7 @@ structure: | | `-- mytemplate.pt | |-- tests.py | `-- views.py + |-- production.ini |-- README.txt |-- setup.cfg `-- setup.py @@ -497,11 +494,18 @@ describe, run, and test your application. written in :term:`ReStructuredText` format. #. ``development.ini`` is a :term:`PasteDeploy` configuration file that can - be used to execute your application. + be used to execute your application during development. + +#. ``production.ini`` is a :term:`PasteDeploy` configuration file that can + be used to execute your application in a production configuration. #. ``setup.cfg`` is a :term:`setuptools` configuration file used by ``setup.py``. +#. ``MANIFEST.in`` is a :term:`distutils` "manifest" file, naming which files + should be included in a source distribution of the package when ``python + setup.py sdist`` is run. + #. ``setup.py`` is the file you'll use to test and distribute your application. It is a standard :term:`setuptools` ``setup.py`` file. @@ -520,8 +524,10 @@ serve``, as well as the deployment settings provided to that application. The generated ``development.ini`` file looks like so: +.. latexbroken? + .. literalinclude:: MyProject/development.ini - :language: guess + :language: ini :linenos: This file contains several "sections" including ``[app:MyProject]``, @@ -599,7 +605,7 @@ or influencing runtime behavior of a :app:`Pyramid` application. See default 'application' (although it's actually a pipeline of middleware and an application) run by ``paster serve`` when it is invoked against this configuration file. The name ``main`` is a convention used by PasteDeploy -signifying that it the default application. +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 @@ -626,6 +632,17 @@ implementations. configuration file. The values in a ``[DEFAULT]`` section will be passed to your application's ``main`` function as ``global_values``. +``production.ini`` +~~~~~~~~~~~~~~~~~~~ + +The ``development.ini`` file is a :term:`PasteDeploy` configuration file with +a purpose much like that of ``development.ini``. However, it disables the +WebError interactive debugger, replacing it with a logger which outputs +exception messages to ``stderr`` by default. It also turns off template +development options such that templates are not automatically reloaded when +changed, and turns off all debugging options. You can use this file instead +of ``development.ini`` when you put your application into production. + .. index:: single: setup.py @@ -693,17 +710,36 @@ who want to use your application. .. warning:: - By default, ``setup.py sdist`` does not place non-Python-source files in - generated tarballs. This means, in this case, that the - ``templates/mytemplate.pt`` file and the files in the ``static`` directory - are not packaged in the tarball. To allow this to happen, check all the - files that you'd like to be distributed along with your application's - Python files into Subversion. After you do this, when you rerun - ``setup.py sdist``, all files checked into the version control system will - be included in the tarball. If you don't use Subversion, and instead use - a different version control system, you may need to install a setuptools - add-on such as ``setuptools-git`` or ``setuptools-hg`` for this behavior - to work properly. + 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 source files* (files ending with a ``.py`` extension) into + tarballs generated by ``python setup.py sdist``. This means, for example, + if your project was not checked into a setuptools-compatible source + control system, and your project directory didn't contain a ``MANIFEST.in`` + file that told the ``sdist`` machinery to include ``*.pt`` files, the + ``myproject/templates/mytemplate.pt`` file would not be included in the + generated tarball. + + Projects generated by Pyramid paster templates include a default + ``MANIFEST.in`` file. The ``MANIFEST.in`` file contains declarations + which tell it to include files like ``*.pt``, ``*.css`` and ``*.js`` in + the generated tarball. If you include 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. + + 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 be put into the generated tarball. To allow this to + happen, check all the files that you'd like to be distributed along with + your application's Python files into Subversion. After you do this, when + you rerun ``setup.py sdist``, all files checked into the version control + system will be included in the tarball. If you don't use Subversion, and + instead use a different version control system, you may need to install a + setuptools add-on such as ``setuptools-git`` or ``setuptools-hg`` for this + behavior to work properly. ``setup.cfg`` ~~~~~~~~~~~~~ @@ -874,9 +910,6 @@ represent the root. This directory contains static assets which support the ``mytemplate.pt`` template. It includes CSS and images. -.. index:: - single: tests.py - ``templates/mytemplate.pt`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -890,6 +923,9 @@ Templates are accessed and used by view configurations and sometimes by view functions themselves. See :ref:`templates_used_directly` and :ref:`templates_used_as_renderers`. +.. index:: + single: tests.py + ``tests.py`` ~~~~~~~~~~~~ @@ -908,6 +944,8 @@ example. See :ref:`testing_chapter` for more information about writing :app:`Pyramid` unit tests. +.. _modifying_package_structure: + Modifying Package Structure ---------------------------- @@ -956,12 +994,14 @@ To this: .. code-block:: python :linenos: - config.add_view('myproject.views.blogs.my_view', + config.add_view('myproject.views.blog.my_view', renderer='myproject:templates/mytemplate.pt') You can then continue to add files to the ``views`` directory, and refer to -views or handler classes/functions within those files via the dotted name -passed as the first argument to ``add_view``. For example: +view classes or functions within those files via the dotted name passed as +the first argument to ``add_view``. For example, if you added a file named +``anothermodule.py`` to the ``views`` subdirectory, and added a view callable +named ``my_view`` to it: .. code-block:: python :linenos: diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 3804fcf42..0b7cdb834 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -3,18 +3,10 @@ Renderers ========= -In the :ref:`views_chapter` chapter, we said that a view callable must -return a :term:`Response` object. We lied. A :term:`renderer` is a service -that attempts to convert a non-Response return value of a function, class, or -instance that acts as a :term:`view callable` to a :term:`Response` object. - -Overview --------- - -A view needn't *always* return a Response object. If a view happens to -return something which does not implement the Pyramid Response interface, -:app:`Pyramid` will attempt to use a :term:`renderer` to construct a -response. For example: +A view needn't *always* return a :term:`Response` object. If a view +happens to return something which does not implement the Pyramid +Response interface, :app:`Pyramid` will attempt to use a +:term:`renderer` to construct a response. For example: .. code-block:: python :linenos: @@ -22,6 +14,7 @@ response. For example: from pyramid.response import Response from pyramid.view import view_config + @view_config(renderer='json') def hello_world(request): return {'content':'Hello!'} @@ -30,7 +23,7 @@ dictionary does not implement the Pyramid response interface, so you might believe that this example would fail. However, since a ``renderer`` is associated with the view callable through its :term:`view configuration` (in this case, using a ``renderer`` argument passed to -:func:`pyramid.view.view_config`), if the view does *not* return a Response +:func:`~pyramid.view.view_config`), if the view does *not* return a Response object, the renderer will attempt to convert the result of the view to a response on the developer's behalf. @@ -67,7 +60,7 @@ object serialization techniques. View configuration can vary the renderer associated with a view callable via the ``renderer`` attribute. For example, this call to -:meth:`pyramid.config.Configurator.add_view` associates the ``json`` renderer +:meth:`~pyramid.config.Configurator.add_view` associates the ``json`` renderer with a view callable: .. code-block:: python @@ -86,7 +79,7 @@ If the :term:`view callable` associated with a :term:`view configuration` returns a Response object directly (an object with the attributes ``status``, ``headerlist`` and ``app_iter``), any renderer associated with the view configuration is ignored, and the response is passed back to :app:`Pyramid` -unmolested. For example, if your view callable returns an instance of the +unchanged. For example, if your view callable returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class as a response, no renderer will be employed. @@ -194,7 +187,7 @@ values serializable by :func:`json.dumps`. You can configure a view to use the JSON renderer by naming ``json`` as the ``renderer`` argument of a view configuration, e.g. by using -:meth:`pyramid.config.Configurator.add_view`: +:meth:`~pyramid.config.Configurator.add_view`: .. code-block:: python :linenos: @@ -358,7 +351,7 @@ to influence associated response attributes. e.g. ``text/xml``. ``response_headerlist`` - A sequence of tuples describing cookie values that should be set in the + A sequence of tuples describing header values that should be set in the response, e.g. ``[('Set-Cookie', 'abc=123'), ('X-My-Header', 'foo')]``. ``response_status`` @@ -396,21 +389,17 @@ documentation in :ref:`request_module`. .. _adding_and_overriding_renderers: -Adding and Overriding Renderers -------------------------------- +Adding and Changing Renderers +----------------------------- New templating systems and serializers can be associated with :app:`Pyramid` renderer names. To this end, configuration declarations can be made which -override an existing :term:`renderer factory`, and which add a new renderer +change an existing :term:`renderer factory`, and which add a new renderer factory. Renderers can be registered imperatively using the :meth:`pyramid.config.Configurator.add_renderer` API. -.. note:: The tasks described in this section can also be performed via - :term:`declarative configuration`. See - :ref:`zcml_adding_and_overriding_renderers`. - For example, to add a renderer which renders views which have a ``renderer`` attribute that is a path that ends in ``.jinja2``: @@ -439,21 +428,20 @@ following interface: class RendererFactory: def __init__(self, info): - """ Constructor: ``info`` will be an object having the - the following attributes: ``name`` (the renderer name), ``package`` - (the package that was 'current' at the time the renderer was - registered), ``type`` (the renderer type name), ``registry`` - (the current application registry) and ``settings`` (the - deployment settings dictionary). - """ + """ Constructor: info will be an object having the the + following attributes: name (the renderer name), package + (the package that was 'current' at the time the + renderer was registered), type (the renderer type + name), registry (the current application registry) and + settings (the deployment settings dictionary). """ def __call__(self, value, system): - """ Call a the renderer implementation with the value and - the system value passed in as arguments and return the - result (a string or unicode object). The value is the - return value of a view. The system value is a dictionary - containing available system values (e.g. ``view``, - ``context``, and ``request``). """ + """ Call a the renderer implementation with the value + and the system value passed in as arguments and return + the result (a string or unicode object). The value is + the return value of a view. The system value is a + dictionary containing available system values + (e.g. view, context, and request). """ The formal interface definition of the ``info`` object passed to a renderer factory constructor is available as :class:`pyramid.interfaces.IRendererInfo`. @@ -468,14 +456,14 @@ There are essentially two different kinds of renderer factories: such as a template. - A renderer factory which expects to accept a token that does not represent - a filesystem path or a asset specification in the ``name`` + a filesystem path or an asset specification in the ``name`` attribute of the ``info`` object fed to its constructor. These renderer factories are registered with a ``name`` value that does not begin with a dot. These renderer factories are typically object serializers. .. sidebar:: Asset Specifications - A asset specification is a colon-delimited identifier for a + An asset specification is a colon-delimited identifier for an :term:`asset`. The colon separates a Python :term:`package` name from a package subpath. For example, the asset specification ``my.package:static/baz.css`` identifies the file named @@ -483,7 +471,7 @@ There are essentially two different kinds of renderer factories: :term:`package`. Here's an example of the registration of a simple renderer factory via -:meth:`pyramid.config.Configurator.add_renderer`: +:meth:`~pyramid.config.Configurator.add_renderer`: .. code-block:: python :linenos: @@ -511,8 +499,8 @@ At startup time, when a :term:`view configuration` is encountered, which has a ``name`` attribute that does not contain a dot, the full ``name`` value is used to construct a renderer from the associated renderer factory. In this case, the view configuration will create an instance -of an ``AMFRenderer`` for each view configuration which includes ``amf`` -as its renderer value. The ``name`` passed to the ``AMFRenderer`` +of an ``MyAMFRenderer`` for each view configuration which includes ``amf`` +as its renderer value. The ``name`` passed to the ``MyAMFRenderer`` constructor will always be ``amf``. Here's an example of the registration of a more complicated renderer @@ -545,15 +533,12 @@ typically the filename extension. This extension is used to look up a renderer factory for the configured view. Then the value of ``renderer`` is passed to the factory to create a renderer for the view. In this case, the view configuration will create an instance of a -``Jinja2Renderer`` for each view configuration which includes anything +``MyJinja2Renderer`` for each view configuration which includes anything ending with ``.jinja2`` in its ``renderer`` value. The ``name`` passed -to the ``Jinja2Renderer`` constructor will be the full value that was +to the ``MyJinja2Renderer`` constructor will be the full value that was set as ``renderer=`` in the view configuration. -See also :ref:`renderer_directive` and -:meth:`pyramid.config.Configurator.add_renderer`. - -Overriding an Existing Renderer +Changing an Existing Renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can associate more than one filename extension with the same existing @@ -570,7 +555,7 @@ extension for the same kinds of templates. For example, to associate the After you do this, :app:`Pyramid` will treat templates ending in both the ``.pt`` and ``.zpt`` filename extensions as Chameleon ZPT templates. -To override the default mapping in which files with a ``.pt`` extension are +To change the default mapping in which files with a ``.pt`` extension are rendered via a Chameleon ZPT page template renderer, use a variation on the following in your application's startup code: @@ -592,3 +577,44 @@ the ``name`` attribute to the renderer tag: config.add_renderer(None, 'mypackage.json_renderer_factory') +Overriding A Renderer At Runtime +-------------------------------- + +.. warning:: This is an advanced feature, not typically used by "civilians". + +In some circumstances, it is necessary to instruct the system to ignore the +static renderer declaration provided by the developer in view configuration, +replacing the renderer with another *after a request starts*. For example, +an "omnipresent" XML-RPC implementation that detects that the request is from +an XML-RPC client might override a view configuration statement made by the +user instructing the view to use a template renderer with one that uses an +XML-RPC renderer. This renderer would produce an XML-RPC representation of +the data returned by an arbitrary view callable. + +To use this feature, create a :class:`~pyramid.events.NewRequest` +:term:`subscriber` which sniffs at the request data and which conditionally +sets an ``override_renderer`` attribute on the request itself, which is the +*name* of a registered renderer. For example: + +.. code-block:: python + :linenos: + + from pyramid.event import subscriber + from pyramid.event import NewRequest + + @subscriber(NewRequest) + def set_xmlrpc_params(event): + request = event.request + if (request.content_type == 'text/xml' + and request.method == 'POST' + and not 'soapaction' in request.headers + and not 'x-pyramid-avoid-xmlrpc' in request.headers): + params, method = parse_xmlrpc_request(request) + request.xmlrpc_params, request.xmlrpc_method = params, method + request.is_xmlrpc = True + request.override_renderer = 'xmlrpc' + return True + +The result of such a subscriber will be to replace any existing static +renderer configured by the developer with a (notional, nonexistent) XML-RPC +renderer if the request appears to come from an XML-RPC client. diff --git a/docs/narr/resourcelocation.rst b/docs/narr/resourcelocation.rst deleted file mode 100644 index 8ddc890ed..000000000 --- a/docs/narr/resourcelocation.rst +++ /dev/null @@ -1,103 +0,0 @@ -.. index:: - single: resource location - -.. _resourcelocation_chapter: - -Resource Location and View Lookup ---------------------------------- - -:app:`Pyramid` uses two separate but cooperating subsystems to find and -invoke :term:`view callable` code written by the application developer: -:term:`resource location` and :term:`view lookup`. - -- First, a :app:`Pyramid` :term:`resource location` subsystem is given a - :term:`request`; it is responsible for finding a :term:`resource` object - based on information present in the request. When a resource is found via - resource location, it becomes known as the :term:`context`. - -- Next, using the context resource found by :term:`resource location` and the - :term:`request`, :term:`view lookup` is then responsible for finding and - invoking a :term:`view callable`. A view callable is a specific bit of - code written and registered by the application developer which receives the - :term:`request` and which returns a :term:`response`. - -These two subsystems are used by :app:`Pyramid` serially: first, a -:term:`resource location` subsystem does its job. Then the result of -resource location is passed to the :term:`view lookup` subsystem. The view -lookup system finds a :term:`view callable` written by an application -developer, and invokes it. A view callable returns a :term:`response`. The -response is returned to the requesting user. - -There are two separate :term:`resource location` subsystems in -:app:`Pyramid`: :term:`traversal` and :term:`URL dispatch`. They can be used -separately or they can be combined. Three chapters which follow describe -:term:`resource location`: :ref:`traversal_chapter`, -:ref:`urldispatch_chapter` and :ref:`hybrid_chapter`. - -There is only one :term:`view lookup` subsystem present in :app:`Pyramid`. -Where appropriate, we will describe how view lookup interacts with context -finding. One chapter which follows describes :term:`view lookup`: -:ref:`views_chapter`. - -Should I Use Traversal or URL Dispatch for Resource Location? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When you use :app:`Pyramid`, you have a choice about how you'd like to -resolve URLs to code: you can use either :term:`traversal` or :term:`URL -dispatch`. The choice to use traversal vs. URL dispatch is largely -"religious". Since :app:`Pyramid` provides support for both approaches, you -can use either exclusively or combine them as you see fit. - -:term:`URL dispatch` is very straightforward. When you limit your -application to using URL dispatch, you know every URL that your application -might generate or respond to, all the URL matching elements are listed in a -single place, and you needn't think about :term:`resource location` or -:term:`view lookup` at all. - -URL dispatch can easily handle URLs such as -``http://example.com/members/Chris``, where it's assumed that each item -"below" ``members`` in the URL represents a single member in some system. -You just match everything "below" ``members`` to a particular :term:`view -callable`, e.g. ``/members/{memberid}``. - -However, URL dispatch is not very convenient if you'd like your URLs to -represent an arbitrary-depth hierarchy. For example, if you need to infer -the difference between sets of URLs such as these, where the ``document`` in -the first URL represents a PDF document, and ``/stuff/page`` in the second -represents an OpenOffice document in a "stuff" folder. - -.. code-block:: text - - http://example.com/members/Chris/document - http://example.com/members/Chris/stuff/page - -It takes more pattern matching assertions to be able to make hierarchies work -in URL-dispatch based systems, and some assertions just aren't possible. -URL-dispatch based systems just don't deal very well with URLs that represent -arbitrary-depth hierarchies. - -:term:`URL dispatch` tends to collapse the two steps of :term:`resource -location` and :term:`view lookup` into a single step. Thus, a URL can map -*directly* to a view callable. This makes URL dispatch easier to understand -than traversal, because traversal makes you understand how :term:`resource -location` works. But explicitly locating a resource provides extra -flexibility. For example, it makes it possible to protect your application -with declarative context-sensitive instance-level :term:`authorization`. - -Unlike URL dispatch, :term:`traversal` works well for URLs that represent -arbitrary-depth hierarchies. Since the path segments that compose a URL are -addressed separately, it becomes very easy to form URLs that represent -arbitrary depth hierarchies in a system that uses traversal. When you're -willing to treat your application resources as a tree that can be traversed, -it also becomes easy to provide "instance-level security": you just attach an -:term:`ACL` security declaration to each resource in the tree. This is not -nearly as easy to do when using URL dispatch. - -Traversal probably just doesn't make any sense when you possess completely -"square" data stored in a relational database because it requires the -construction and maintenance of a resource tree and requires that the -developer think about mapping URLs to code in terms of traversing that tree. - -We'll examine both :term:`URL dispatch` and :term:`traversal` in the next two -chapters. - diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index f90b1eb12..a11466a87 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -1,21 +1,23 @@ +.. _resources_chapter: + Resources ========= -A :term:`resource` is an object that represents a "place" in a tree related -to your application. Every :app:`Pyramid` application has at least one -resource object: the :term:`root` resource (even if you don't define one -manually, a default root resource is created for you). The root resource is -the root of a :term:`resource tree`. A resource tree is a set of nested -dictionary-like objects which you can use to represent your website's -structure. +A :term:`resource` is an object that represents a "place" in a tree +related to your application. Every :app:`Pyramid` application has at +least one resource object: the :term:`root` resource. Even if you don't +define a root resource manually, a default one is created for you. The +root resource is the root of a :term:`resource tree`. A resource tree +is a set of nested dictionary-like objects which you can use to +represent your website's structure. In an application which uses :term:`traversal` to map URLs to code, the -resource tree structure is used heavily to map a URL to a :term:`view -callable`. :app:`Pyramid` will walk "up" the resource tree by traversing -through the nested dictionary structure of the tree when :term:`traversal` is -used in order to find a :term:`context` resource. Once a context resource is -found, the context resource and data in the request will be used to find a -:term:`view callable`. +resource tree structure is used heavily to map each URL to a :term:`view +callable`. When :term:`traversal` is used, :app:`Pyramid` will walk +through the resource tree by traversing through its nested dictionary +structure in order to find a :term:`context` resource. Once a context +resource is found, the context resource and data in the request will be +used to find a :term:`view callable`. In an application which uses :term:`URL dispatch`, the resource tree is only used indirectly, and is often "invisible" to the developer. In URL dispatch @@ -26,7 +28,7 @@ much less important in applications that use URL dispatch than applications that use traversal. In "Zope-like" :app:`Pyramid` applications, resource objects also often store -data persistently and offer methods related to mutating that persistent data. +data persistently, and offer methods related to mutating that persistent data. In these kinds of applications, resources not only represent the site structure of your website, but they become the :term:`domain model` of the application. @@ -34,8 +36,8 @@ application. Also: - The ``context`` and ``containment`` predicate arguments to - :meth:`pyramid.config.Configurator.add_view` (or a - :func:`pyramid.view.view_config` decorator) reference a resource class + :meth:`~pyramid.config.Configurator.add_view` (or a + :func:`~pyramid.view.view_config` decorator) reference a resource class or resource :term:`interface`. - A :term:`root factory` returns a resource. @@ -44,7 +46,7 @@ Also: view. - Various helpful :app:`Pyramid` API methods expect a resource as an - argument (e.g. :func:`pyramid.url.resource_url` and others). + argument (e.g. :func:`~pyramid.url.resource_url` and others). .. index:: single: resource tree @@ -72,8 +74,8 @@ tree: the container's ``__getitem__`` should return the sub-resource. - Leaf resources, which do not contain other resources, must not implement a - ``__getitem__``, or if they do, their ``__getitem__`` method must raise a - :exc:`KeyError`. + ``__getitem__``, or if they do, their ``__getitem__`` method must always + raise a :exc:`KeyError`. See :ref:`traversal_chapter` for more information about how traversal works against resource instances. @@ -160,9 +162,9 @@ you will reach the filesystem root directory. .. warning:: If your root resource has a ``__name__`` argument that is not ``None`` or the empty string, URLs returned by the - :func:`pyramid.url.resource_url` function and paths generated by - the :func:`pyramid.traversal.resource_path` and - :func:`pyramid.traversal.resource_path_tuple` APIs will be + :func:`~pyramid.url.resource_url` function and paths generated by + the :func:`~pyramid.traversal.resource_path` and + :func:`~pyramid.traversal.resource_path_tuple` APIs will be generated improperly. The value of ``__name__`` will be prepended to every path and URL generated (as opposed to a single leading slash or empty tuple element). @@ -184,7 +186,7 @@ you will reach the filesystem root directory. objects "by hand". Instead, as necessary, during traversal :app:`Pyramid` will wrap each resource (even the root resource) in a ``LocationProxy`` which will dynamically assign a ``__name__`` and a ``__parent__`` to the - traversed resrouce (based on the last traversed resource and the name + traversed resource (based on the last traversed resource and the name supplied to ``__getitem__``). The root resource will have a ``__name__`` attribute of ``None`` and a ``__parent__`` attribute of ``None``. @@ -207,6 +209,8 @@ location-aware. single: resource_url pair: generating; resource url +.. _generating_the_url_of_a_resource: + Generating The URL Of A Resource -------------------------------- @@ -227,7 +231,7 @@ The simplest call to :func:`~pyramid.url.resource_url` looks like this: url = resource_url(resource, request) The ``request`` passed to ``resource_url`` in the above example is an -instance of an :app:`Pyramid` :term:`request` object. +instance of a :app:`Pyramid` :term:`request` object. If the resource referred to as ``resource`` in the above example was the root resource, and the host that was used to contact the server was @@ -239,7 +243,7 @@ A slash is appended to all resource URLs when :func:`~pyramid.url.resource_url` is used to generate them in this simple manner, because resources are "places" in the hierarchy, and URLs are meant to be clicked on to be visited. Relative URLs that you include on HTML pages -rendered as the result of the default view of a resource are typically more +rendered as the result of the default view of a resource are more apt to be relative to these resources than relative to their parent. You can also pass extra elements to :func:`~pyramid.url.resource_url`: @@ -277,7 +281,7 @@ about virtually rooting a resource. The shortcut method of the :term:`request` named :meth:`pyramid.request.Request.resource_url` can be used instead of -:func:`pyramid.url.resource_url` to generate a resource URL. +:func:`~pyramid.url.resource_url` to generate a resource URL. For more information about generating resource URLs, see the documentation for :func:`pyramid.url.resource_url`. @@ -288,13 +292,13 @@ Overriding Resource URL Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If a resource object implements a ``__resource_url__`` method, this method -will be called when :func:`pyramid.url.resource_url` is called to generate a +will be called when :func:`~pyramid.url.resource_url` is called to generate a URL for the resource, overriding the default URL returned for the resource by :func:`~pyramid.url.resource_url`. The ``__resource_url__`` hook is passed two arguments: ``request`` and ``info``. ``request`` is the :term:`request` object passed to -:func:`pyramid.url.resource_url`. ``info`` is a dictionary with two +:func:`~pyramid.url.resource_url`. ``info`` is a dictionary with two keys: ``physical_path`` @@ -324,6 +328,11 @@ would have been what was returned anyway, but your code can perform arbitrary logic as necessary. For example, your code may wish to override the hostname or port number of the generated URL. +Note that the URL generated by ``__resource_url__`` should be fully +qualified, should end in a slash, and should not contain any query string or +anchor elements (only path elements) to work best with +:func:`~pyramid.url.resource_url`. + Generating the Path To a Resource --------------------------------- @@ -341,7 +350,7 @@ character. If ``resource`` in the example above was accessible in the tree as ``root['a']['b']``, the above example would generate the string ``/a/b``. -Any positional arguments passed in to :func:`pyramid.traversal.resource_path` +Any positional arguments passed in to :func:`~pyramid.traversal.resource_path` will be appended as path segments to the end of the resource path. .. code-block:: python @@ -399,7 +408,7 @@ Obtaining the Lineage of a Resource ----------------------------------- :func:`pyramid.location.lineage` returns a generator representing the -:term:`lineage` of the :term:`location` aware:term:`resource` object. +:term:`lineage` of the :term:`location` aware :term:`resource` object. The :func:`~pyramid.location.lineage` function returns the resource it is passed, then each parent of the resource, in order. For example, if the @@ -528,7 +537,7 @@ declares that the blog entry implements an :term:`interface`. implements(IBlogEntry) def __init__(self, title, body, author): self.title = title - self.body = body + self.body = body self.author = author self.created = datetime.datetime.now() @@ -563,7 +572,7 @@ To do so, use the :func:`zope.interface.directlyProvides` function: class BlogEntry(object): def __init__(self, title, body, author): self.title = title - self.body = body + self.body = body self.author = author self.created = datetime.datetime.now() @@ -591,7 +600,7 @@ the :func:`zope.interface.alsoProvides` function: class BlogEntry(object): def __init__(self, title, body, author): self.title = title - self.body = body + self.body = body self.author = author self.created = datetime.datetime.now() @@ -609,7 +618,7 @@ configuration, see :ref:`using_resource_interfaces`. Finding a Resource With a Class or Interface in Lineage ------------------------------------------------------- -Use the :func:`pyramid.traversal.find_interface` API to locate a parent that +Use the :func:`~pyramid.traversal.find_interface` API to locate a parent that is of a particular Python class, or which implements some :term:`interface`. For example, if your resource tree is composed as follows: @@ -660,7 +669,7 @@ These can be used to walk down a resource tree, or conveniently locate one resource "inside" another. Some APIs in :ref:`security_module` accept a resource object as a parameter. -For example, the :func:`pyramid.security.has_permission` API accepts a +For example, the :func:`~pyramid.security.has_permission` API accepts a resource object as one of its arguments; the ACL is obtained from this resource or one of its ancestors. Other APIs in the :mod:`pyramid.security` module also accept :term:`context` as an argument, and a context is always a diff --git a/docs/narr/router.rst b/docs/narr/router.rst index d3d5bd370..11f84d4ea 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -29,10 +29,10 @@ processing? #. The :term:`application registry` and the :term:`request` object created in the last step are pushed on to the :term:`thread local` stack that :app:`Pyramid` uses to allow the functions named - :func:`pyramid.threadlocal.get_current_request` and - :func:`pyramid.threadlocal.get_current_registry` to work. + :func:`~pyramid.threadlocal.get_current_request` and + :func:`~pyramid.threadlocal.get_current_registry` to work. -#. A :class:`pyramid.events.NewRequest` :term:`event` is sent to any +#. A :class:`~pyramid.events.NewRequest` :term:`event` is sent to any subscribers. #. If any :term:`route` has been defined within application @@ -44,9 +44,9 @@ processing? #. If any route matches, the request is mutated; a ``matchdict`` and ``matched_route`` attributes are added to the request object; the - former contains a dictionary representign the matched dynamic + former contains a dictionary representing the matched dynamic elements of the request's ``PATH_INFO`` value, the latter contains - the :class:`pyramid.interfaces.IRoute` object representing the + 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 a ``factory`` argument, this factory is @@ -74,7 +74,7 @@ processing? they can be accessed via e.g. ``request.context`` within :term:`view` code. -#. A :class:`pyramid.events.ContextFound` :term:`event` is +#. A :class:`~pyramid.events.ContextFound` :term:`event` is sent to any subscribers. #. :app:`Pyramid` looks up a :term:`view` callable using the @@ -83,7 +83,7 @@ processing? the context, the type of the request, and the value of the view name, and any :term:`predicate` attributes applied to the view configuration), :app:`Pyramid` raises a - :class:`pyramid.exceptions.NotFound` exception, which is meant + :class:`~pyramid.exceptions.NotFound` exception, which is meant to be caught by a surrounding exception handler. #. If a view callable was found, :app:`Pyramid` attempts to call @@ -97,14 +97,14 @@ processing? security information attached to the context. If it returns ``True``, :app:`Pyramid` calls the view callable to obtain a response. If it returns ``False``, it raises a - :class:`pyramid.exceptions.Forbidden` exception, which is meant + :class:`~pyramid.exceptions.Forbidden` exception, which is meant to be called by a surrounding exception handler. #. If any exception was raised within a :term:`root factory`, by :term:`traversal`, by a :term:`view callable` or by :app:`Pyramid` itself (such as when it raises - :class:`pyramid.exceptions.NotFound` or - :class:`pyramid.exceptions.Forbidden`), the router catches the + :class:`~pyramid.exceptions.NotFound` or + :class:`~pyramid.exceptions.Forbidden`), the router catches the exception, and attaches it to the request as the ``exception`` attribute. It then attempts to find a :term:`exception view` for the exception that was caught. If it finds an exception view @@ -116,15 +116,15 @@ processing? successfully generated by a normal :term:`view callable` or an :term:`exception view` callable. :app:`Pyramid` will attempt to execute any :term:`response callback` functions attached via - :meth:`pyramid.request.Request.add_response_callback`. A - :class:`pyramid.events.NewResponse` :term:`event` is then sent to any + :meth:`~pyramid.request.Request.add_response_callback`. A + :class:`~pyramid.events.NewResponse` :term:`event` is then sent to any subscribers. The response object's ``app_iter``, ``status``, and ``headerlist`` attributes are then used to generate a WSGI response. The response is sent back to the upstream WSGI server. #. :app:`Pyramid` will attempt to execute any :term:`finished callback` functions attached via - :meth:`pyramid.request.Request.add_finished_callback`. + :meth:`~pyramid.request.Request.add_finished_callback`. #. The :term:`thread local` stack is popped. @@ -133,6 +133,6 @@ processing? This is a very high-level overview that leaves out various details. For more detail about subsystems invoked by the :app:`Pyramid` router such as traversal, URL dispatch, views, and event processing, see -:ref:`resourcelocation_chapter`, :ref:`views_chapter`, and +:ref:`urldispatch_chapter`, :ref:`views_chapter`, and :ref:`events_chapter`. diff --git a/docs/narr/security.rst b/docs/narr/security.rst index c5262faa2..c7a07b857 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -6,20 +6,20 @@ Security ======== -:app:`Pyramid` provides an optional declarative authorization system that -prevents a :term:`view` from being invoked when the user represented by -credentials in the :term:`request` does not have an appropriate level of -access when a particular resource is the :term:`context`. Here's how it -works at a high level: +:app:`Pyramid` provides an optional declarative authorization system +that can prevent a :term:`view` from being invoked based on an +:term:`authorization policy`. Before a view is invoked, the +authorization system can use the credentials in the :term:`request` +along with the :term:`context` resource to determine if access will be +allowed. Here's how it works at a high level: -- A :term:`request` is generated when a user visits our application. +- A :term:`request` is generated when a user visits the application. - Based on the request, a :term:`context` resource is located through :term:`resource location`. A context is located differently depending on whether the application uses :term:`traversal` or :term:`URL dispatch`, but a context is ultimately found in either case. See - :ref:`resourcelocation_chapter` for more information about resource - location. + the :ref:`urldispatch_chapter` chapter for more information. - A :term:`view callable` is located by :term:`view lookup` using the context as well as other attributes of the request. @@ -41,6 +41,15 @@ works at a high level: - If the authorization policy denies access, the view callable is not invoked; instead the :term:`forbidden view` is invoked. +Security in :app:`Pyramid`, unlike many systems, cleanly and explicitly +separates authentication and authorization. Authentication is merely the +mechanism by which credentials provided in the :term:`request` are +resolved to one or more :term:`principal` identifiers. These identifiers +represent the users and groups in effect during the request. +Authorization then determines access based on the :term:`principal` +identifiers, the :term:`view callable` being invoked, and the +:term:`context` resource. + Authorization is enabled by modifying your application to include an :term:`authentication policy` and :term:`authorization policy`. :app:`Pyramid` comes with a variety of implementations of these @@ -63,14 +72,14 @@ Enabling an Authorization Policy Imperatively ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Passing an ``authorization_policy`` argument to the constructor of the -:class:`pyramid.config.Configurator` class enables an +:class:`~pyramid.config.Configurator` class enables an authorization policy. You must also enable an :term:`authentication policy` in order to enable the authorization policy. This is because authorization, in general, depends upon authentication. Use the ``authentication_policy`` argument to the -:class:`pyramid.config.Configurator` class during +:class:`~pyramid.config.Configurator` class during application setup to specify an authentication policy. For example: @@ -88,7 +97,7 @@ For example: authorization_policy=authorization_policy) .. note:: the ``authentication_policy`` and ``authorization_policy`` - arguments may also be passed to the Configurator as :ref:`dotted + arguments may also be passed to the Configurator as :term:`dotted Python name` values, each representing the dotted name path to a suitable implementation global defined at Python module scope. @@ -107,9 +116,6 @@ See also the :mod:`pyramid.authorization` and :mod:`pyramid.authentication` modules for alternate implementations of authorization and authentication policies. -You can also enable a security policy declaratively via ZCML. See -:ref:`zcml_authorization_policy`. - .. index:: single: permissions single: protecting views @@ -155,9 +161,6 @@ may be performed via the ``@view_config`` decorator: """ Add blog entry code goes here """ pass -Or the same thing can be done using the ``permission`` attribute of the ZCML -:ref:`view_directive` directive. - As a result of any of these various view configuration statements, if an authorization policy is in place when the view callable is found during normal application operations, the requesting user will need to possess the @@ -170,8 +173,8 @@ to invoke the ``blog_entry_add_view`` view. If he does not, the Setting a Default Permission ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If a permission is not supplied to a view configuration, the -registered view always be executable by entirely anonymous users: any +If a permission is not supplied to a view configuration, the registered +view will always be executable by entirely anonymous users: any authorization policy in effect is ignored. In support of making it easier to configure applications which are @@ -184,25 +187,28 @@ These APIs are in support of configuring a default permission for an application: - The ``default_permission`` constructor argument to the - :mod:`pyramid.config.Configurator` constructor. - -- The - :meth:`pyramid.config.Configurator.set_default_permission` - method. + :mod:`~pyramid.config.Configurator` constructor. -- The :ref:`default_permission_directive` ZCML directive. +- The :meth:`pyramid.config.Configurator.set_default_permission` method. When a default permission is registered: -- if a view configuration names an explicit ``permission``, the default +- If a view configuration names an explicit ``permission``, the default permission is ignored for that view registration, and the view-configuration-named permission is used. -- if a view configuration names an explicit permission as the string +- If a view configuration names an explicit permission as the string ``__no_permission_required__``, the default permission is ignored, and the view is registered *without* a permission (making it available to all callers regardless of their credentials). +.. warning:: + + When you register a default permission, *all* views (even :term:`exception + view` views) are protected by a permission. For all views which are truly + meant to be anonymously accessible, you will need to associate the view's + configuration with the ``__no_permission_required__`` permission. + .. index:: single: ACL single: access control list @@ -305,9 +311,7 @@ authentication system provides group information and the effective :term:`authentication policy` policy is written to respect group information. For example, the :class:`pyramid.authentication.RepozeWho1AuthenicationPolicy` respects group -information if you configure it with a ``callback``. See -:ref:`authentication_policies_directives_section` for more information about -the ``callback`` attribute. +information if you configure it with a ``callback``. Each ACE in an ACL is processed by an authorization policy *in the order dictated by the ACL*. So if you have an ACL like this: @@ -456,7 +460,7 @@ parents left. In order to allow the security machinery to perform ACL inheritance, resource objects must provide *location-awareness*. Providing *location-awareness* means two things: the root object in the resource tree must have a -``_name__`` attribute and a ``__parent__`` attribute. +``__name__`` attribute and a ``__parent__`` attribute. .. code-block:: python :linenos: @@ -538,7 +542,7 @@ one of :data:`pyramid.security.ACLAllowed`, ``msg`` attribute, which is a string indicating why the permission was denied or allowed. Introspecting this information in the debugger or via print statements when a call to -:func:`pyramid.security.has_permission` fails is often useful. +:func:`~pyramid.security.has_permission` fails is often useful. .. index:: single: authentication policy (creating) @@ -560,15 +564,28 @@ that implements the following interface: class AuthenticationPolicy(object): """ An object representing a Pyramid authentication policy. """ + def authenticated_userid(self, request): """ Return the authenticated userid or ``None`` if no - authenticated userid can be found. """ + authenticated userid can be found. This method of the policy + should ensure that a record exists in whatever persistent store is + used related to the user (the user should not have been deleted); + if a record associated with the current id does not exist in a + persistent store, it should return ``None``.""" + + def unauthenticated_userid(self, request): + """ Return the *unauthenticated* userid. This method performs the + same duty as ``authenticated_userid`` but is permitted to return the + userid based only on data present in the request; it needn't (and + shouldn't) check any persistent store to ensure that the user record + related to the request userid exists.""" def effective_principals(self, request): """ Return a sequence representing the effective principals including the userid and any groups belonged to by the current - user, including 'system' groups such as Everyone and - Authenticated. """ + user, including 'system' groups such as + ``pyramid.security.Everyone`` and + ``pyramid.security.Authenticated``. """ def remember(self, request, principal, **kw): """ Return a set of headers suitable for 'remembering' the @@ -581,7 +598,7 @@ that implements the following interface: current user on subsequent requests. """ After you do so, you can pass an instance of such a class into the -:class:`pyramid.config.Configurator` class at configuration +:class:`~pyramid.config.Configurator` class at configuration time as ``authentication_policy`` to use it. .. index:: @@ -600,7 +617,7 @@ otherwise specified. In some cases, it's useful to be able to use a different authorization policy than the default -:class:`pyramid.authorization.ACLAuthorizationPolicy`. For +:class:`~pyramid.authorization.ACLAuthorizationPolicy`. For example, it might be desirable to construct an alternate authorization policy which allows the application to use an authorization mechanism that does not involve :term:`ACL` objects. @@ -617,13 +634,19 @@ following interface: class IAuthorizationPolicy(object): """ An object representing a Pyramid authorization policy. """ def permits(self, context, principals, permission): - """ Return True if any of the principals is allowed the - permission in the current context, else return False """ + """ Return ``True`` if any of the ``principals`` is allowed the + ``permission`` in the current ``context``, else return ``False`` + """ def principals_allowed_by_permission(self, context, permission): - """ Return a set of principal identifiers allowed by the - permission """ + """ Return a set of principal identifiers allowed by the + ``permission`` in ``context``. This behavior is optional; if you + choose to not implement it you should define this method as + something which raises a ``NotImplementedError``. This method + will only be called when the + ``pyramid.security.principals_allowed_by_permission`` API is + used.""" After you do so, you can pass an instance of such a class into the -:class:`pyramid.config.Configurator` class at configuration +:class:`~pyramid.config.Configurator` class at configuration time as ``authorization_policy`` to use it. diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index de9add3b7..97e3ebc55 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -3,13 +3,18 @@ .. _sessions_chapter: -Session Objects -=============== +Sessions +======== A :term:`session` is a namespace which is valid for some period of continual activity that can be used to represent a user's interaction with a web application. +This chapter describes how to configure sessions, what session +implementations :app:`Pyramid` provides out of the box, how to store and +retrieve data from sessions, and two session-specific features: flash +messages, and cross-site request forgery attack prevention. + .. _using_the_default_session_factory: Using The Default Session Factory @@ -32,12 +37,12 @@ limitation: representation of the session is fewer than 4000. This is suitable only for very small data sets. -It is, however, digitally signed, and thus its data cannot easily be +It is digitally signed, however, and thus its data cannot easily be tampered with. You can configure this session factory in your :app:`Pyramid` application by using the ``session_factory`` argument to the -:class:`pyramid.config.Configurator` class: +:class:`~pyramid.config.Configurator` class: .. code-block:: python :linenos: @@ -113,7 +118,7 @@ documentation. Some gotchas: - Keys and values of session data must be *pickleable*. This means, - typically, that they must be instances of basic types of objects, + typically, that they are instances of basic types of objects, such as strings, lists, dictionaries, tuples, integers, etc. If you place an object in a session data key or value that is not pickleable, an error will be raised when the session is serialized. @@ -162,3 +167,180 @@ both types are available in :class:`pyramid.interfaces.ISession`. You might use the cookie implementation in the :mod:`pyramid.session` module as inspiration. +.. index:: + single: flash messages + +Flash Messages +-------------- + +"Flash messages" are simply a queue of message strings stored in the +:term:`session`. To use flash messaging, you must enable a :term:`session +factory` as described in :ref:`using_the_default_session_factory` or +:ref:`using_alternate_session_factories`. + +Flash messaging has two main uses: to display a status message only once to +the user after performing an internal redirect, and to allow generic code to +log messages for single-time display without having direct access to an HTML +template. The user interface consists of a number of methods of the +:term:`session` object. + +Using the ``session.flash`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To add a message to a flash message queue, use a session object's ``flash()`` +method: + +.. code-block:: python + + request.session.flash('mymessage') + +The ``flash()`` method appends a message to a flash queue, creating the queue +if necessary. + +``flash()`` accepts three arguments: + +.. method:: flash(message, queue='', allow_duplicate=True) + +The ``message`` argument is required. It represents a message you wish to +later display to a user. It is usually a string but the ``message`` you +provide is not modified in any way. + +The ``queue`` argument allows you to choose a queue to which to append +the message you provide. This can be used to push different kinds of +messages into flash storage for later display in different places on a +page. You can pass any name for your queue, but it must be a string. +Each queue is independent, and can be popped by ``pop_flash()`` or +examined via ``peek_flash()`` separately. ``queue`` defaults to the +empty string. The empty string represents the default flash message +queue. + +.. code-block:: python + + request.session.flash(msg, 'myappsqueue') + +The ``allow_duplicate`` argument defaults to ``True``. If this is +``False``, and you attempt to add a message value which is already +present in the queue, it will not be added. + +Using the ``session.pop_flash`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once one or more messages have been added to a flash queue by the +``session.flash()`` API, the ``session.pop_flash()`` API can be used to +pop an entire queue and return it for use. + +To pop a particular queue of messages from the flash object, use the session +object's ``pop_flash()`` method. This returns a list of the messages +that were added to the flash queue, and empties the queue. + +.. method:: pop_flash(queue='') + +.. code-block:: python + :linenos: + + >>> 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. + +.. code-block:: python + :linenos: + + >>> request.session.flash('info message') + >>> request.session.pop_flash() + ['info message'] + >>> request.session.pop_flash() + [] + +Using the ``session.peek_flash`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once one or more messages has been added to a flash queue by the +``session.flash()`` API, the ``session.peek_flash()`` API can be used to +"peek" at that queue. Unlike ``session.pop_flash()``, the queue is not +popped from flash storage. + +.. method:: peek_flash(queue='') + +.. code-block:: python + :linenos: + + >>> 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 + single: cross-site request forgery attacks, prevention + +Preventing Cross-Site Request Forgery Attacks +--------------------------------------------- + +`Cross-site request forgery +<http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ attacks are a +phenomenon whereby a user with an identity on your website might click on a +URL or button on another website which unwittingly redirects the user to your +application to perform some command that requires elevated privileges. + +You can avoid most of these attacks by making sure that the correct *CSRF +token* has been set in an :app:`Pyramid` session object before performing any +actions in code which requires elevated privileges that is invoked via a form +post. To use CSRF token support, you must enable a :term:`session factory` +as described in :ref:`using_the_default_session_factory` or +:ref:`using_alternate_session_factories`. + +Using the ``session.get_csrf_token`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To get the current CSRF token from the session, use the +``session.get_csrf_token()`` method. + +.. code-block:: python + + token = request.session.get_csrf_token() + +The ``session.get_csrf_token()`` method accepts no arguments. It returns a +CSRF *token* string. If ``session.get_csrf_token()`` or +``session.new_csrf_token()`` was invoked previously for this session, the +existing token will be returned. If no CSRF token previously existed for +this session, a new token will be will be set into the session and returned. +The newly created token will be opaque and randomized. + +You can use the returned token as the value of a hidden field in a form that +posts to a method that requires elevated privileges. The handler for the +form post should use ``session.get_csrf_token()`` *again* to obtain the +current CSRF token related to the user from the session, and compare it to +the value of the hidden form field. For example, if your form rendering +included the CSRF token obtained via ``session.get_csrf_token()`` as a hidden +input field named ``csrf_token``: + +.. code-block:: python + :linenos: + + token = request.session.get_csrf_token() + if token != request.POST['csrf_token']: + raise ValueError('CSRF token did not match') + +Using the ``session.new_csrf_token`` Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To explicitly add a new CSRF token to the session, 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 1b21cb516..e2c43b17e 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -82,7 +82,7 @@ Here's a high-level time-ordered overview of what happens when you press Our generated ``development.ini`` file looks like so: .. literalinclude:: MyProject/development.ini - :language: guess + :language: ini :linenos: In this case, the ``myproject.__init__:main`` function referred to by the @@ -94,7 +94,7 @@ Here's a high-level time-ordered overview of what happens when you press 'default_locale_name':'en'}``. #. The ``main`` function first constructs a - :class:`pyramid.config.Configurator` instance, passing a root resource + :class:`~pyramid.config.Configurator` instance, passing a root resource factory (constructor) to it as its ``root_factory`` argument, and ``settings`` dictionary captured via the ``**settings`` kwarg as its ``settings`` argument. @@ -109,18 +109,18 @@ Here's a high-level time-ordered overview of what happens when you press ``debug_authorization``, etc. #. The ``main`` function then calls various methods on the an instance of the - class :class:`pyramid.config.Configurator` method. The intent of + class :class:`~pyramid.config.Configurator` method. The intent of calling these methods is to populate an :term:`application registry`, which represents the :app:`Pyramid` configuration related to the application. -#. The :meth:`pyramid.config.Configurator.make_wsgi_app` method is called. +#. The :meth:`~pyramid.config.Configurator.make_wsgi_app` method is called. The result is a :term:`router` instance. The router is associated with the :term:`application registry` implied by the configurator previously populated by other methods run against the Configurator. The router is a WSGI application. -#. A :class:`pyramid.events.ApplicationCreated` event is emitted (see +#. A :class:`~pyramid.events.ApplicationCreated` event is emitted (see :ref:`events_chapter` for more information about events). #. Assuming there were no errors, the ``main`` function in ``myproject`` @@ -142,7 +142,7 @@ Deployment Settings ------------------- Note that an augmented version of the values passed as ``**settings`` to the -:class:`pyramid.config.Configurator` constructor will be available in +:class:`~pyramid.config.Configurator` constructor will be available in :app:`Pyramid` :term:`view callable` code as ``request.registry.settings``. You can create objects you wish to access later from view code, and put them into the dictionary you pass to the configurator as ``settings``. They will diff --git a/docs/narr/static.rst b/docs/narr/static.rst deleted file mode 100644 index 53564a632..000000000 --- a/docs/narr/static.rst +++ /dev/null @@ -1,258 +0,0 @@ -Static Assets -============= - -:app:`Pyramid` makes it possible to serve up static asset files from a -directory on a filesystem. This chapter describes how to configure -:app:`Pyramid` to do so. - -.. index:: - single: add_static_view - -.. _static_assets_section: - -Serving Static Assets ---------------------- - -Use the :meth:`pyramid.config.Configurator.add_static_view` to instruct -:app:`Pyramid` to serve static assets such as JavaScript and CSS files. This -mechanism makes static files available at a name relative to the application -root URL, e.g. ``/static``. - -Note that the ``path`` provided to -:meth:`pyramid.config.Configurator.add_static_view` may be a fully qualified -:term:`asset specification`, or an *absolute path*. - -Here's an example of a use of -:meth:`pyramid.config.Configurator.add_static_view` that will serve -files up under the ``/static`` URL from the ``/var/www/static`` directory of -the computer which runs the :app:`Pyramid` application using an absolute -path. - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - config.add_static_view(name='static', path='/var/www/static') - -Here's an example of :meth:`pyramid.config.Configurator.add_static_view` that -will serve files up under the ``/static`` URL from the ``a/b/c/static`` -directory of the Python package named ``some_package`` using a fully -qualified :term:`asset specification`. - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - config.add_static_view(name='static', path='some_package:a/b/c/static') - -Whether you use for ``path`` a fully qualified asset specification, or an -absolute path, when you place your static files on the filesystem in the -directory represented as the ``path`` of the directive, you will then be able -to view the static files in this directory via a browser at URLs prefixed -with the directive's ``name``. For instance if the ``static`` directive's -``name`` is ``static`` and the static directive's ``path`` is -``/path/to/static``, ``http://localhost:6543/static/foo.js`` will return the -file ``/path/to/static/dir/foo.js``. The static directory may contain -subdirectories recursively, and any subdirectories may hold files; these will -be resolved by the static view as you would expect. - -While the ``path`` argument can be a number of different things, the ``name`` -argument of the call to :meth:`pyramid.config.Configurator.add_static_view` -can also be one of a number of things: a *view name* or a *URL*. The above -examples have shown usage of the ``name`` argument as a view name. When -``name`` is a *URL* (or any string with a slash (``/``) in it), static assets -can be served from an external webserver. In this mode, the ``name`` is used -as the URL prefix when generating a URL using :func:`pyramid.url.static_url`. - -For example, :meth:`pyramid.config.Configurator.add_static_view` may -be fed a ``name`` argument which is ``http://example.com/images``: - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - config.add_static_view(name='http://example.com/images', - path='mypackage:images') - -Because :meth:`pyramid.config.Configurator.add_static_view` is -provided with a ``name`` argument that is the URL prefix -``http://example.com/images``, subsequent calls to -:func:`pyramid.url.static_url` with paths that start with the ``path`` -argument passed to :meth:`pyramid.config.Configurator.add_static_view` -will generate a URL something like ``http://example.com/images/logo.png``. The -external webserver listening on ``example.com`` must be itself configured to -respond properly to such a request. The :func:`pyramid.url.static_url` API -is discussed in more detail later in this chapter. - -The :ref:`static_directive` ZCML directive offers an declarative equivalent -to :meth:`pyramid.config.Configurator.add_static_view`. Use of the -:ref:`static_directive` ZCML directive is completely equivalent to using -imperative configuration for the same purpose. - -.. note:: - - Using :func:`pyramid.url.static_url` in conjunction with a - :meth:`pyramid.configuration.Configurator.add_static_view` makes it - possible to put static media on a separate webserver during production (if - the ``name`` argument to - :meth:`pyramid.config.Configurator.add_static_view` is a URL), - while keeping static media package-internal and served by the development - webserver during development (if the ``name`` argument to - :meth:`pyramid.config.Configurator.add_static_view` is a view - name). To create such a circumstance, we suggest using the - :attr:`pyramid.registry.Registry.settings` API in conjunction with a - setting in the application ``.ini`` file named ``media_location``. Then - set the value of ``media_location`` to either a view name or a URL - depending on whether the application is being run in development or in - production (use a different `.ini`` file for production than you do for - development). This is just a suggestion for a pattern; any setting name - other than ``media_location`` could be used. - -.. index:: - single: generating static asset urls - single: static asset urls - -.. _generating_static_asset_urls: - -Generating Static Asset URLs -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When a :meth:`pyramid.config.Configurator.add_static_view` method is used to -register a static asset directory, a special helper API named -:func:`pyramid.url.static_url` can be used to generate the appropriate URL -for an asset that lives in one of the directories named by the static -registration ``path`` attribute. - -For example, let's assume you create a set of static declarations like so: - -.. code-block:: python - :linenos: - - config.add_static_view(name='static1', path='mypackage:assets/1') - config.add_static_view(name='static2', path='mypackage:assets/2') - -These declarations create URL-accessible directories which have URLs that -begin with ``/static1`` and ``/static2``, respectively. The assets in the -``assets/1`` directory of the ``mypackage`` package are consulted when a user -visits a URL which begins with ``/static1``, and the assets in the -``assets/2`` directory of the ``mypackage`` package are consulted when a user -visits a URL which begins with ``/static2``. - -You needn't generate the URLs to static assets "by hand" in such a -configuration. Instead, use the :func:`pyramid.url.static_url` API to -generate them for you. For example: - -.. code-block:: python - :linenos: - - from pyramid.url import static_url - from pyramid.chameleon_zpt import render_template_to_response - - def my_view(request): - css_url = static_url('mypackage:assets/1/foo.css', request) - js_url = static_url('mypackage:assets/2/foo.js', request) - return render_template_to_response('templates/my_template.pt', - css_url = css_url, - js_url = js_url) - -If the request "application URL" of the running system is -``http://example.com``, the ``css_url`` generated above would be: -``http://example.com/static1/foo.css``. The ``js_url`` generated -above would be ``http://example.com/static2/foo.js``. - -One benefit of using the :func:`pyramid.url.static_url` function rather than -constructing static URLs "by hand" is that if you need to change the ``name`` -of a static URL declaration, the generated URLs will continue to resolve -properly after the rename. - -URLs may also be generated by :func:`pyramid.url.static_url` to static assets -that live *outside* the :app:`Pyramid` application. This will happen when -the :meth:`pyramid.config.Configurator.add_static_view` API associated with -the path fed to :func:`pyramid.url.static_url` is a *URL* instead of a view -name. For example, the ``name`` argument may be ``http://example.com`` while -the the ``path`` given may be ``mypackage:images``: - -.. code-block:: python - :linenos: - - config.add_static_view(name='http://example.com/images', path='mypackage:images') - -Under such a configuration, the URL generated by ``static_url`` for -assets which begin with ``mypackage:images`` will be prefixed with -``http://example.com/images``: - -.. code-block:: python - :linenos: - - static_url('mypackage:images/logo.png', request) - # -> http://example.com/images/logo.png - -.. index:: - single: static assets view - -Advanced: Serving Static Assets Using a View Callable ------------------------------------------------------ - -For more flexibility, static assets can be served by a :term:`view callable` -which you register manually. For example, you may want static assets to only -be available when the :term:`context` is of a particular type, or when -certain request headers are present. - -The :class:`pyramid.view.static` helper class is used to perform this -task. This class creates an object that is capable acting as a :app:`Pyramid` -view callable which serves static assets from a directory. For instance, to -serve files within a directory located on your filesystem at -``/path/to/static/dir`` from the URL path ``/static`` in your application, -create an instance of the :class:`pyramid.view.static` class inside a -``static.py`` file in your application root as below. - -.. ignore-next-block -.. code-block:: python - :linenos: - - from pyramid.view import static - static_view = static('/path/to/static/dir') - -.. note:: the argument to :class:`pyramid.view.static` can also be - a "here-relative" pathname, e.g. ``my/static`` (meaning relative to the - Python package of the module in which the view is being defined). - It can also be a :term:`asset specification` - (e.g. ``anotherpackage:some/subdirectory``). - -Subsequently, you may wire this view up to be accessible as ``/static`` using -the :mod:`pyramid.config.Configurator.add_view` method in your application's -startup code against either the class or interface that represents your root -resource object. - -.. code-block:: python - :linenos: - - config.add_view('mypackage.static.static_view', name='static', - context='mypackage.resources.Root') - -In this case, ``mypackage.resources.Root`` refers to the class of your -:app:`Pyramid` application's resource tree. - -The context argument above limits where the static view is accessible to URL -paths directly under the root object. If you omit the ``context`` argument, -then ``static`` will be accessible as the static view against any resource -object in the resource tree. This will allow ``/static/foo.js`` to work, but -it will also allow for ``/anything/static/foo.js`` too, as long as -``anything`` can be resolved. - -Note that you cannot use the :func:`pyramid.url.static_url` API to generate -URLs against assets made accessible by registering a custom static view. - -.. warning:: - - When adding a static view to your root object, you need to be careful that - there are no resource objects contained in the root with the same key as - the view name (e.g., ``static``). Resource objects take precedence during - traversal, thus such a name collision will cause the resource to "shadow" - your static view. To avoid this issue, and ensure that your root - resource's ``__getitem__`` is never called when a static asset is - requested, you can refer to them unambiguously using the ``@@`` prefix - (goggles) in their URLs. For the above examples you could use - '/@@static/foo.js' instead of '/static/foo.js' to avoid such shadowing. - See :ref:`traversal_chapter` for information about "goggles" (``@@``). - diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 437b823e9..426ec229b 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -203,7 +203,7 @@ may set attributes on the response that influence these values. Here's an example of changing the content-type and status of the response object returned by -:func:`pyramid.renderers.render_to_response`: +:func:`~pyramid.renderers.render_to_response`: .. code-block:: python :linenos: @@ -219,7 +219,7 @@ response object returned by return response Here's an example of manufacturing a response object using the result -of :func:`pyramid.renderers.render` (a string): +of :func:`~pyramid.renderers.render` (a string): .. code-block:: python :linenos: @@ -247,8 +247,8 @@ System Values Used During Rendering ----------------------------------- When a template is rendered using -:func:`pyramid.renderers.render_to_response` or -:func:`pyramid.renderers.render`, the renderer representing the +:func:`~pyramid.renderers.render_to_response` or +:func:`~pyramid.renderers.render`, the renderer representing the template will be provided with a number of *system* values. These values are provided in a dictionary to the renderer and include: @@ -282,7 +282,7 @@ variables. Templates Used as Renderers via Configuration --------------------------------------------- -An alternative to using :func:`pyramid.renderers.render_to_response` +An alternative to using :func:`~pyramid.renderers.render_to_response` to render templates manually in your view callable code, is to specify the template as a :term:`renderer` in your *view configuration*. This can be done with any of the @@ -299,7 +299,7 @@ The association of a template as a renderer for a :term:`view configuration` makes it possible to replace code within a :term:`view callable` that handles the rendering of a template. -Here's an example of using a :class:`pyramid.view.view_config` +Here's an example of using a :class:`~pyramid.view.view_config` decorator to specify a :term:`view configuration` that names a template renderer: @@ -448,7 +448,8 @@ Here's what a simple :term:`Chameleon` ZPT template used under <body> <h1 class="title">Welcome to <code>${project}</code>, an application generated by the <a - href="http://pylonshq.com/pyramid">pyramid</a> web + href="http://docs.pylonsproject.org/projects/pyramid/dev/" + >pyramid</a> web application framework.</h1> </body> </html> @@ -456,8 +457,8 @@ Here's what a simple :term:`Chameleon` ZPT template used under Note the use of :term:`Genshi` -style ``${replacements}`` above. This is one of the ways that :term:`Chameleon` ZPT differs from standard ZPT. The above template expects to find a ``project`` key in the set -of keywords passed in to it via :func:`pyramid.renderers.render` or -:func:`pyramid.renderers.render_to_response`. Typical ZPT +of keywords passed in to it via :func:`~pyramid.renderers.render` or +:func:`~pyramid.renderers.render_to_response`. Typical ZPT attribute-based syntax (e.g. ``tal:content`` and ``tal:replace``) also works in these templates. @@ -480,7 +481,7 @@ passing the macro template, or even the macro itself, *into* the rendered template. To do this you can use the :func:`pyramid.renderers.get_renderer` API to retrieve the macro template, and pass it into the template being rendered via the dictionary returned by the view. For example, using a -:term:`view configuration` via a :class:`pyramid.view.view_config` decorator +:term:`view configuration` via a :class:`~pyramid.view.view_config` decorator that uses a :term:`renderer`: .. code-block:: python @@ -606,7 +607,7 @@ configure your application development environment so that exceptions generated by Chameleon during template compilation and execution will contain nicer debugging information. -.. warning:: template-debugging behavior is not recommended for +.. warning:: Template-debugging behavior is not recommended for production sites as it slows renderings; it's usually only desirable during development. @@ -628,7 +629,7 @@ application's configuration section, e.g.: .. code-block:: ini :linenos: - [app:main] + [app:MyProject] use = egg:MyProject#app debug_templates = true @@ -697,10 +698,9 @@ has built-in bindings for the Mako templating system. The language definition documentation for Mako templates is available from `the Mako website <http://www.makotemplates.org/>`_. -To use a Mako template, given a :term:`Mako` ZPT template file named -``foo.mak`` in the ``templates`` subdirectory in your application -package named ``mypackage``, you can configure the template as a -:term:`renderer` like so: +To use a Mako template, given a :term:`Mako` template file named ``foo.mak`` +in the ``templates`` subdirectory in your application package named +``mypackage``, you can configure the template as a :term:`renderer` like so: .. code-block:: python :linenos: @@ -733,24 +733,20 @@ look like: .. code-block:: xml :linenos: - <!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" - xmlns:tal="http://xml.zope.org/namespaces/tal"> + <html> <head> - <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>${project} Application</title> </head> <body> <h1 class="title">Welcome to <code>${project}</code>, an application generated by the <a - href="http://pylonshq.com/pyramid">pyramid</a> web - application framework.</h1> + href="http://docs.pylonsproject.org/projects/pyramid/dev/" + >pyramid</a> web application framework.</h1> </body> </html> This template doesn't use any advanced features of Mako, only the -``${squiggly}`` replacement syntax for names that are passed in as +``${}`` replacement syntax for names that are passed in as :term:`renderer globals`. See the `the Mako documentation <http://www.makotemplates.org/>`_ to use more advanced features. @@ -769,7 +765,7 @@ appear immediately without needing to restart the application process. environment so that a change to a template will be automatically detected, and the template will be reloaded on the next rendering. -.. warning:: auto-template-reload behavior is not recommended for +.. warning:: Auto-template-reload behavior is not recommended for production sites as it slows rendering slightly; it's usually only desirable during development. diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst index 007b96c2a..bd45388c2 100644 --- a/docs/narr/testing.rst +++ b/docs/narr/testing.rst @@ -78,81 +78,84 @@ See :ref:`threadlocals_chapter` for information about these functions and the data structures they return. If your code uses these ``get_current_*`` functions or calls :app:`Pyramid` -code which uses ``get_current_*`` functions, you will need to construct a -:term:`Configurator` and call its ``begin`` method within the ``setUp`` -method of your unit test and call the same Configurator's ``end`` method -within the ``tearDown`` method of your unit test. - -We'll also instruct the Configurator we use during testing to *autocommit*. -Normally when a Configurator is used by an application, it defers performing -any "real work" until its ``.commit`` method is called (often implicitly by -the :meth:`pyramid.config.Configurator.make_wsgi_app` method). Passing -``autocommit=True`` to the Configurator constructor causes the Configurator -to perform all actions implied by methods called on it immediately, which is -more convenient for unit-testing purposes than needing to call -:meth:`pyramid.config.Configurator.commit` in each test. - -The use of a Configurator and its ``begin`` and ``end`` methods allows you to -supply each unit test method in a test case with an environment that has an -isolated registry and an isolated request for the duration of a single test. -Here's an example of using this feature: +code which uses ``get_current_*`` functions, you will need to call +:func:`pyramid.testing.setUp` in your test setup and you will need to call +:func:`pyramid.testing.tearDown` in your test teardown. +:func:`~pyramid.testing.setUp` pushes a registry onto the :term:`thread +local` stack, which makes the ``get_current_*`` functions work. It returns a +:term:`Configurator` object which can be used to perform extra configuration +required by the code under test. :func:`~pyramid.testing.tearDown` pops the +thread local stack. + +Normally when a Configurator is used directly with the ``main`` block of +a Pyramid application, it defers performing any "real work" until its +``.commit`` method is called (often implicitly by the +:meth:`pyramid.config.Configurator.make_wsgi_app` method). The +Configurator returned by :func:`~pyramid.testing.setUp` is an +*autocommitting* Configurator, however, which performs all actions +implied by methods called on it immediately. This is more convenient +for unit-testing purposes than needing to call +:meth:`pyramid.config.Configurator.commit` in each test after adding +extra configuration statements. + +The use of the :func:`~pyramid.testing.setUp` and +:func:`~pyramid.testing.tearDown` functions allows you to supply each unit +test method in a test case with an environment that has an isolated registry +and an isolated request for the duration of a single test. Here's an example +of using this feature: .. code-block:: python :linenos: import unittest - from pyramid.config import Configurator + from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() The above will make sure that -:func:`pyramid.threadlocal.get_current_registry` will return the -:term:`application registry` associated with the ``config`` Configurator -instance when :func:`pyramid.threadlocal.get_current_registry` is called in a -test case method attached to ``MyTest``. Each test case method attached to -``MyTest`` will use an isolated registry. - -The :meth:`pyramid.config.Configurator.begin` method accepts various -arguments that influence the code run during the test. See the -:ref:`configuration_module` chapter for information about the API of a -:term:`Configurator`, including its ``begin`` and ``end`` methods. - -If you also want to make :func:`pyramid.get_current_request` return something +:func:`~pyramid.threadlocal.get_current_registry` called within a test +case method of ``MyTest`` will return the :term:`application registry` +associated with the ``config`` Configurator instance. Each test case +method attached to ``MyTest`` will use an isolated registry. + +The :func:`~pyramid.testing.setUp` and :func:`~pyramid.testing.tearDown` +functions accepts various arguments that influence the environment of the +test. See the :ref:`testing_module` chapter for information about the extra +arguments supported by these functions. + +If you also want to make :func:`~pyramid.get_current_request` return something other than ``None`` during the course of a single test, you can pass a -:term:`request` object into the :meth:`pyramid.config.Configurator.begin` -method of the Configurator within the ``setUp`` method of your test: +:term:`request` object into the :func:`pyramid.testing.setUp` within the +``setUp`` method of your test: .. code-block:: python :linenos: import unittest - from pyramid.config import Configurator from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) request = testing.DummyRequest() - self.config.begin(request=request) + self.config = testing.setUp(request=request) def tearDown(self): - self.config.end() - -If you pass a :term:`request` object into the ``begin`` method of the -configurator within your test case's ``setUp``, any test method attached to -the ``MyTest`` test case that directly or indirectly calls -:func:`pyramid.threadlocal.get_current_request` will receive the request you -passed into the ``begin`` method. Otherwise, during testing, -:func:`pyramid.threadlocal.get_current_request` will return ``None``. We use -a "dummy" request implementation supplied by -:class:`pyramid.testing.DummyRequest` because it's easier to construct than a -"real" :app:`Pyramid` request object. + testing.tearDown() + +If you pass a :term:`request` object into :func:`pyramid.testing.setUp` +within your test case's ``setUp``, any test method attached to the +``MyTest`` test case that directly or indirectly calls +:func:`~pyramid.threadlocal.get_current_request` will receive the request +object. Otherwise, during testing, +:func:`~pyramid.threadlocal.get_current_request` will return ``None``. +We use a "dummy" request implementation supplied by +:class:`pyramid.testing.DummyRequest` because it's easier to construct +than a "real" :app:`Pyramid` request object. What? ~~~~~ @@ -160,20 +163,20 @@ What? Thread local data structures are always a bit confusing, especially when they're used by frameworks. Sorry. So here's a rule of thumb: if you don't *know* whether you're calling code that uses the -:func:`pyramid.threadlocal.get_current_registry` or -:func:`pyramid.threadlocal.get_current_request` functions, or you don't care -about any of this, but you still want to write test code, just always create -an autocommitting Configurator instance and call its ``begin`` method within -the ``setUp`` of a unit test, then subsequently call its ``end`` method in -the test's ``tearDown``. This won't really hurt anything if the application -you're testing does not call any ``get_current*`` function. +:func:`~pyramid.threadlocal.get_current_registry` or +:func:`~pyramid.threadlocal.get_current_request` functions, or you don't care +about any of this, but you still want to write test code, just always call +:func:`pyramid.testing.setUp` in your test's ``setUp`` method and +:func:`pyramid.testing.tearDown` in your tests' ``tearDown`` method. This +won't really hurt anything if the application you're testing does not call +any ``get_current*`` function. .. index:: single: pyramid.testing single: Configurator testing API Using the ``Configurator`` and ``pyramid.testing`` APIs in Unit Tests ------------------------------------------------------------------------- +--------------------------------------------------------------------- The ``Configurator`` API and the ``pyramid.testing`` module provide a number of functions which can be used during unit testing. These functions make @@ -187,29 +190,29 @@ function. .. code-block:: python :linenos: + from pyramid.security import has_permission + from pyramid.exceptions import Forbidden + def view_fn(request): - from pyramid.chameleon_zpt import render_template_to_response - if 'say' in request.params: - return render_template_to_response('templates/submitted.pt', - say=request.params['say']) - return render_template_to_response('templates/show.pt', say='Hello') - -Without invoking any startup code or using the testing API, an attempt to run -this view function in a unit test will result in an error. When a -:app:`Pyramid` application starts normally, it will populate a -:term:`application registry` using :term:`configuration declaration` calls -made against a :term:`Configurator` (sometimes deferring to the application's -``configure.zcml`` :term:`ZCML` file via ``load_zcml``). But if this -application registry is not created and populated (e.g. with an -:meth:`pyramid.config.Configurator.add_view` :term:`configuration -declaration` or ``view`` declarations in :term:`ZCML`), like when you invoke -application code via a unit test, :app:`Pyramid` API functions will tend to -fail. + if not has_permission('edit', request.context, request): + raise Forbidden + return {'greeting':'hello'} + +Without doing anything special during a unit test, the call to +:func:`~pyramid.security.has_permission` in this view function will always +return a ``True`` value. When a :app:`Pyramid` application starts normally, +it will populate a :term:`application registry` using :term:`configuration +declaration` calls made against a :term:`Configurator`. But if this +application registry is not created and populated (e.g. by initializing the +configurator with an authorization policy), like when you invoke application +code via a unit test, :app:`Pyramid` API functions will tend to either fail +or return default results. So how do you test the branch of the code in this +view function that raises :exc:`Forbidden`? The testing API provided by :app:`Pyramid` allows you to simulate various application registry registrations for use under a unit testing framework without needing to invoke the actual application configuration implied by its -``run.py``. For example, if you wanted to test the above ``view_fn`` +``main`` function. For example, if you wanted to test the above ``view_fn`` (assuming it lived in the package named ``my.package``), you could write a :class:`unittest.TestCase` that used the testing API. @@ -217,72 +220,68 @@ without needing to invoke the actual application configuration implied by its :linenos: import unittest - from pyramid.config import Configurator from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() - def test_view_fn_not_submitted(self): + def test_view_fn_forbidden(self): + from pyramid.exceptions import Forbidden from my.package import view_fn - renderer = self.config.testing_add_renderer('templates/show.pt') + self.config.testing_securitypolicy(userid='hank', + permissive=False) request = testing.DummyRequest() - response = view_fn(request) - renderer.assert_(say='Hello') + request.context = testing.DummyResource() + self.assertRaises(Forbidden, view_fn, request) - def test_view_fn_submitted(self): + def test_view_fn_allowed(self): + from pyramid.exceptions import Forbidden from my.package import view_fn - renderer = self.config.testing_add_renderer( - 'templates/submitted.pt') + self.config.testing_securitypolicy(userid='hank', + permissive=True) request = testing.DummyRequest() - request.params['say'] = 'Yo' + request.context = testing.DummyResource() response = view_fn(request) - renderer.assert_(say='Yo') - + self.assertEqual(response, {'greeting':'hello'}) + In the above example, we create a ``MyTest`` test case that inherits from :mod:`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. -The first test method, ``test_view_fn_not_submitted`` tests the ``view_fn`` -function in the case that no "form" values (represented by request.params) -have been submitted. Its first line registers a "dummy template renderer" -named ``templates/show.pt`` via the -:meth:`pyramid.config.Configurator.testing_add_renderer` method; this method -returns a :class:`pyramid.testing.DummyTemplateRenderer` instance which we -hang on to for later. +The first test method, ``test_view_fn_forbidden`` tests the ``view_fn`` when +the authentication policy forbids the current user the ``edit`` permission. +Its third line registers a "dummy" "non-permissive" authorization policy +using the :meth:`~pyramid.config.Configurator.testing_securitypolicy` method, +which is a special helper method for unit testing. We then create a :class:`pyramid.testing.DummyRequest` object which simulates a WebOb request object API. A :class:`pyramid.testing.DummyRequest` is a request object that requires less setup than a "real" :app:`Pyramid` request. We call the function being tested with the manufactured request. When the -function is called, :func:`pyramid.chameleon_zpt.render_template_to_response` -will call the "dummy" template renderer object instead of the real template -renderer object. When the dummy renderer is called, it will set attributes -on itself corresponding to the non-path keyword arguments provided to the -:func:`pyramid.chameleon_zpt.render_template_to_response` function. We check -that the ``say`` parameter sent into the template rendering function was -``Hello`` in this specific example. The ``assert_`` method of the renderer -we've created will raise an :exc:`AssertionError` if the value passed to the -renderer as ``say`` does not equal ``Hello`` (any number of keyword arguments -are supported). - -The second test method, named ``test_view_fn_submitted`` tests the alternate -case, where the ``say`` form value has already been set in the request and -performs a similar template registration and assertion. We assert at the end -of this that the renderer's ``say`` attribute is ``Yo``, as this is what is -expected of the view function in the branch it's testing. - -Note that the test calls the :meth:`pyramid.config.Configurator.begin` method -in its ``setUp`` method and the ``end`` method of the same in its -``tearDown`` method. If you use any of the -:class:`pyramid.config.Configurator` APIs during testing, be sure to use this -pattern in your test case's ``setUp`` and ``tearDown``; these methods make -sure you're using a "fresh" :term:`application registry` per test run. +function is called, :func:`pyramid.security.has_permission` will call the +"dummy" authentication policy we've registered through +:meth:`~pyramid.config.Configuration.testing_securitypolicy`, which denies +access. We check that the view function raises a :exc:`Forbidden` error. + +The second test method, named ``test_view_fn_allowed`` tests the alternate +case, where the authentication policy allows access. Notice that we pass +different values to +:meth:`~pyramid.config.Configurator.testing_securitypolicy` to obtain this +result. We assert at the end of this that the view function returns a value. + +Note that the test calls the :func:`pyramid.testing.setUp` function in its +``setUp`` method and the :func:`pyramid.testing.tearDown` function in its +``tearDown`` method. We assign the result of :func:`pyramid.testing.setUp` +as ``config`` on the unittest class. This is a :term:`Configurator` object +and all methods of the configurator can be called as necessary within +tests. If you use any of the :class:`~pyramid.config.Configurator` APIs during +testing, be sure to use this pattern in your test case's ``setUp`` and +``tearDown``; these methods make sure you're using a "fresh" +:term:`application registry` per test run. See the :ref:`testing_module` chapter for the entire :app:`Pyramid` -specific testing API. This chapter describes APIs for registering a security policy, @@ -309,12 +308,13 @@ implementations to give the code under test only enough context to run. some code *and* its integration with the rest of the :app:`Pyramid` framework. -In :app:`Pyramid` applications that use :term:`ZCML`, you can create an -integration test by *loading its ZCML* in the test's setup code. This causes -the entire :app:`Pyramid` environment to be set up and torn down as if your -application was running "for real". This is a heavy-hammer way of making -sure that your tests have enough context to run properly, and it tests your -code's integration with the rest of :app:`Pyramid`. +In :app:`Pyramid` applications that are plugins to Pyramid, you can create an +integration test by including it's ``includeme`` function via +:meth:`pyramid.config.Configurator.include` in the test's setup code. This +causes the entire :app:`Pyramid` environment to be set up and torn down as if +your application was running "for real". This is a heavy-hammer way of +making sure that your tests have enough context to run properly, and it tests +your code's integration with the rest of :app:`Pyramid`. Let's demonstrate this by showing an integration test for a view. The below test assumes that your application's package name is ``myapp``, and that @@ -327,23 +327,21 @@ after accessing some values that require a fully set up environment. import unittest - from pyramid.config import Configurator from pyramid import testing class ViewIntegrationTests(unittest.TestCase): def setUp(self): """ This sets up the application registry with the - registrations your application declares in its configure.zcml - (including dependent registrations for pyramid itself). + registrations your application declares in its ``includeme`` + function. """ import myapp - self.config = Configurator(package=myapp, autocommit=True) - self.config.begin() - self.config.load_zcml('myapp:configure.zcml') + self.config = testing.setUp() + self.config.include('myapp') def tearDown(self): """ Clear out the application registry """ - self.config.end() + testing.tearDown() def test_my_view(self): from myapp.views import my_view @@ -359,7 +357,7 @@ after accessing some values that require a fully set up environment. str(len(body)))) Unless you cannot avoid it, you should prefer writing unit tests that use the -:class:`pyramid.config.Configurator` API to set up the right "mock" +:class:`~pyramid.config.Configurator` API to set up the right "mock" registrations rather than creating an integration test. Unit tests will run faster (because they do less for each test) and the result of a unit test is usually easier to make assertions about. diff --git a/docs/narr/threadlocals.rst b/docs/narr/threadlocals.rst index 171eaa1c7..909f643a0 100644 --- a/docs/narr/threadlocals.rst +++ b/docs/narr/threadlocals.rst @@ -32,7 +32,7 @@ bad idea, at least if code readability counts as an important concern. For historical reasons, however, thread local variables are indeed consulted by various :app:`Pyramid` API functions. For example, the implementation of the :mod:`pyramid.security` function named -:func:`pyramid.security.authenticated_userid` retrieves the thread +:func:`~pyramid.security.authenticated_userid` retrieves the thread local :term:`application registry` as a matter of course to find an :term:`authentication policy`. It uses the :func:`pyramid.threadlocal.get_current_registry` function to @@ -43,8 +43,8 @@ allows arbitrary authentication policies to be "plugged in". When they need to do so, :app:`Pyramid` internals use two API functions to retrieve the :term:`request` and :term:`application -registry`: :func:`pyramid.threadlocal.get_current_request` and -:func:`pyramid.threadlocal.get_current_registry`. The former +registry`: :func:`~pyramid.threadlocal.get_current_request` and +:func:`~pyramid.threadlocal.get_current_registry`. The former returns the "current" request; the latter returns the "current" registry. Both ``get_current_*`` functions retrieve an object from a thread-local data structure. These API functions are documented in @@ -88,17 +88,17 @@ the :mod:`pyramid.scripting` API will never cause any Router code to be executed. However, the :mod:`pyramid.scripting` APIs also push some values on to the thread locals stack as a matter of course. Such scripts should expect the -:func:`pyramid.threadlocal.get_current_request` function to always +:func:`~pyramid.threadlocal.get_current_request` function to always return ``None``, and should expect the -:func:`pyramid.threadlocal.get_current_registry` function to return +:func:`~pyramid.threadlocal.get_current_registry` function to return exactly the same :term:`application registry` for every request. Why You Shouldn't Abuse Thread Locals ------------------------------------- You probably should almost never use the -:func:`pyramid.threadlocal.get_current_request` or -:func:`pyramid.threadlocal.get_current_registry` functions, except +:func:`~pyramid.threadlocal.get_current_request` or +:func:`~pyramid.threadlocal.get_current_registry` functions, except perhaps in tests. In particular, it's almost always a mistake to use ``get_current_request`` or ``get_current_registry`` in application code because its usage makes it possible to write code that can be @@ -111,8 +111,8 @@ follows: each as ``request``). - ``get_current_request`` should never be called in :term:`resource` code. - If a resource needs access to the request, it should be passed the request - by a :term:`view callable`. + If a resource needs access to the request, it should be passed the request + by a :term:`view callable`. - ``get_current_request`` function should never be called because it's "easier" or "more elegant" to think about calling it than to pass a @@ -134,7 +134,7 @@ follows: application depend upon it) means you're forming a dependency in the wrong direction. -Use of the :func:`pyramid.threadlocal.get_current_request` function +Use of the :func:`~pyramid.threadlocal.get_current_request` function in application code *is* still useful in very limited circumstances. As a rule of thumb, usage of ``get_current_request`` is useful **within code which is meant to eventually be removed**. For @@ -151,7 +151,7 @@ time, the older implementation code is disused and the hack that uses ``get_current_request`` is removed. This would be an appropriate place to use the ``get_current_request``. -Use of the :func:`pyramid.threadlocal.get_current_registry` +Use of the :func:`~pyramid.threadlocal.get_current_registry` function should be limited to testing scenarios. The registry made current by use of the :meth:`pyramid.config.Configurator.begin` method during a diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index 2d7878265..b3747be61 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -3,33 +3,22 @@ Traversal ========= -:term:`Traversal` provides an alternative to using :term:`URL dispatch` to -map a URL to a :term:`view callable`. It is the act of locating a -:term:`context` resource by walking over a :term:`resource tree`, starting -from a :term:`root` resource, using a :term:`request` object as a source of -path information. Once a context resource is found, a view callable is -looked up and invoked. - -Using :term:`Traversal` to map a URL to code is optional. It is often less -easy to understand than URL dispatch, so if you're a rank beginner, it -probably makes sense to use URL dispatch to map URLs to code instead of -traversal. In that case, you can skip this chapter. - -.. index:: - single: traversal overview - -A High-Level Overview of Traversal ----------------------------------- - A :term:`traversal` uses the URL (Universal Resource Locator) to find a -:term:`resource`. This is done by mapping each segment of the path portion -of the URL into a set of nested dictionary-like objects called the -:term:`resource tree`. You might think of this as looking up files and -directories in a file system. Traversal walks down the path until it finds a -published "directory" or "file". The resource we find as the result of a -traversal becomes the :term:`context`. A separate :term:`view lookup` -subsystem is used to then find some view code willing "publish" the context -resource. +:term:`resource` located in a :term:`resource tree`, which is a set of +nested dictionary-like objects. Traversal is done by using each segment +of the path portion of the URL to navigate through the :term:`resource +tree`. You might think of this as looking up files and directories in a +file system. Traversal walks down the path until it finds a published +resource, analogous to a file system "directory" or "file". The +resource found as the result of a traversal becomes the +:term:`context` of the :term:`request`. Then, the :term:`view lookup` +subsystem is used to find some view code willing "publish" this +resource by generating a :term:`response`. + +Using :term:`Traversal` to map a URL to code is optional. It is often +less easy to understand than :term:`URL dispatch`, so if you're a rank +beginner, it probably makes sense to use URL dispatch to map URLs to +code instead of traversal. In that case, you can skip this chapter. .. index:: single: traversal details @@ -37,62 +26,65 @@ resource. Traversal Details ----------------- -:term:`Traversal` is dependent on information in a :term:`request` object. -Every :term:`request` object contains URL path information in the -``PATH_INFO`` portion of the :term:`WSGI` environment. The ``PATH_INFO`` -portion of the WSGI environment is the portion of a request's URL following -the hostname and port number, but before any query string elements or +:term:`Traversal` is dependent on information in a :term:`request` +object. Every :term:`request` object contains URL path information in +the ``PATH_INFO`` portion of the :term:`WSGI` environment. The +``PATH_INFO`` string is the portion of a request's URL following the +hostname and port number, but before any query string elements or fragment element. For example the ``PATH_INFO`` portion of the URL ``http://example.com:8080/a/b/c?foo=1`` is ``/a/b/c``. -Traversal treats the ``PATH_INFO`` segment of a URL as a sequence of path -segments. For example, the ``PATH_INFO`` string ``/a/b/c`` is converted to -the sequence ``['a', 'b', 'c']``. +Traversal treats the ``PATH_INFO`` segment of a URL as a sequence of +path segments. For example, the ``PATH_INFO`` string ``/a/b/c`` is +converted to the sequence ``['a', 'b', 'c']``. -After the path info is converted, a lookup is performed against the resource -tree for each path segment. Each lookup uses the ``__getitem__`` method of a -resource in the tree. +This path sequence is then used to descend through the :term:`resource +tree`, looking up a resource for each path segment. Each lookup uses the +``__getitem__`` method of a resource in the tree. For example, if the path info sequence is ``['a', 'b', 'c']``: -- :term:`Traversal` pops the first element (``a``) from the path segment - sequence and attempts to call the root resource's ``__getitem__`` method - using that value (``a``) as an argument; we'll presume it succeeds. +- :term:`Traversal` starts by acquiring the :term:`root` resource of the + application by calling the :term:`root factory`. The :term:`root factory` + can be configured to return whatever object is appropriate as the + traversal root of your application. -- When the root resource's ``__getitem__`` succeeds it will return another - resource, which we'll call "A". The :term:`context` temporarily becomes - the "A" resource. +- Next, the first element (``a``) is popped from the path segment + sequence and is used as a key to lookup the corresponding resource + in the root. This invokes the root resource's ``__getitem__`` method + using that value (``a``) as an argument. + +- If the root resource "contains" a resource with key ``a``, its + ``__getitem__`` method will return it. The :term:`context` temporarily + becomes the "A" resource. - The next segment (``b``) is popped from the path sequence, and the "A" resource's ``__getitem__`` is called with that value (``b``) as an argument; we'll presume it succeeds. -- When the "A" resource's ``__getitem__`` succeeds it will return another - resource, which we'll call "B". The :term:`context` temporarily becomes - the "B" resource. - -This process continues until the path segment sequence is exhausted or a path -element cannot be resolved to a resource. In either case, a :term:`context` -resource is chosen. - -Traversal "stops" when it either reaches a leaf level resource in your -resource tree or when the path segments implied by the URL "run out". The -resource that traversal "stops on" becomes the :term:`context`. If at any -point during traversal any resource in the tree doesn't have a -``__getitem__`` method, or if the ``__getitem__`` method of a resource raises -a :exc:`KeyError`, traversal ends immediately, and that resource becomes the -:term:`context`. - -The results of a :term:`traversal` also include a :term:`view name`. The -:term:`view name` is the *first* URL path segment in the set of ``PATH_INFO`` -segments "left over" in the path segment list popped by the traversal process -*after* traversal finds a context resource. - -The combination of the context resource and the :term:`view name` found via -traversal is used later in the same request by a separate :app:`Pyramid` -subsystem -- the :term:`view lookup` subsystem -- to find a :term:`view -callable` later within the same request. How :app:`Pyramid` performs view -lookup is explained within the :ref:`views_chapter` chapter. +- The "A" resource's ``__getitem__`` returns another resource, which + we'll call "B". The :term:`context` temporarily becomes the "B" + resource. + +Traversal continues until the path segment sequence is exhausted or a +path element cannot be resolved to a resource. In either case, the +:term:`context` resource is the last object that the traversal +successfully resolved. If any resource found during traversal lacks a +``__getitem__`` method, or if its ``__getitem__`` method raises a +:exc:`KeyError`, traversal ends immediately, and that resource becomes +the :term:`context`. + +The results of a :term:`traversal` also include a :term:`view name`. If +traversal ends before the path segment sequence is exhausted, the +:term:`view name` is the *next* remaining path segment element. If the +:term:`traversal` expends all of the path segments, then the :term:`view +name` is the empty string (`''`). + +The combination of the context resource and the :term:`view name` found +via traversal is used later in the same request by the :term:`view +lookup` subsystem to find a :term:`view callable`. How :app:`Pyramid` +performs view lookup is explained within the :ref:`view_config_chapter` +chapter. .. index:: single: object tree @@ -104,19 +96,20 @@ lookup is explained within the :ref:`views_chapter` chapter. The Resource Tree ----------------- -When your application uses :term:`traversal` to resolve URLs to code, the -application must supply a :term:`resource tree` to :app:`Pyramid`. The -resource tree is a set of nested dictionary-like objects. The root of the -tree is represented by a :term:`root` resource. The tree is effectively a -nested set of dictionary-like objects. +The resource tree is a set of nested dictionary-like resource objects +that begins with a :term:`root` resource. In order to use +:term:`traversal` to resolve URLs to code, your application must supply +a :term:`resource tree` to :app:`Pyramid`. -In order to supply a root resource for an application, at system startup -time, the :app:`Pyramid` :term:`Router` is configured with a callback known -as a :term:`root factory`. The root factory is supplied by the application -developer as the ``root_factory`` argument to the application's -:term:`Configurator`. +In order to supply a root resource for an application the :app:`Pyramid` +:term:`Router` is configured with a callback known as a :term:`root +factory`. The root factory is supplied by the application, at startup +time, as the ``root_factory`` argument to the :term:`Configurator`. -Here's an example of a simple root factory: +The root factory is a Python callable that accepts a :term:`request` +object, and returns the root object of the :term:`resource tree`. A +function, or class is typically used as an application's root factory. +Here's an example of a simple root factory class: .. code-block:: python :linenos: @@ -133,24 +126,23 @@ passing it to an instance of a :term:`Configurator` named ``config``: config = Configurator(root_factory=Root) -Using the ``root_factory`` argument to a :class:`pyramid.config.Configurator` -constructor tells your :app:`Pyramid` application to call this root factory -to generate a root resource whenever a request enters the application. This -root factory is also known as the global root factory. A root factory can -alternately be passed to the ``Configurator`` as a :term:`dotted Python name` -which refers to a root factory defined in a different module. - -A root factory is passed a :term:`request` object and it is expected to -return an object which represents the root of the resource tree. All -:term:`traversal` will begin at this root resource. Usually a root factory -for a traversal-based application will be more complicated than the above -``Root`` class; in particular it may be associated with a database connection -or another persistence mechanism. +The ``root_factory`` argument to the +:class:`~pyramid.config.Configurator` constructor registers this root +factory to be called to generate a root resource whenever a request +enters the application. The root factory registered this way is also +known as the global root factory. A root factory can alternately be +passed to the ``Configurator`` as a :term:`dotted Python name` which can +refer to a root factory defined in a different module. If no :term:`root factory` is passed to the :app:`Pyramid` -:term:`Configurator` constructor, or the ``root_factory`` is specified as the -value ``None``, a *default* root factory is used. The default root factory -always returns a resource that has no child resources. +:term:`Configurator` constructor, or if the ``root_factory`` value +specified is ``None``, a *default* root factory is used. The default +root factory always returns a resource that has no child resources; it +is effectively empty. + +Usually a root factory for a traversal-based application will be more +complicated than the above ``Root`` class; in particular it may be +associated with a database connection or another persistence mechanism. .. sidebar:: Emulating the Default Root Factory @@ -225,13 +217,14 @@ We'll provide a description of the algorithm, a diagram of how the algorithm works, and some example traversal scenarios that might help you understand how the algorithm operates against a specific resource tree. -We'll also talk a bit about :term:`view lookup`. The :ref:`views_chapter` -chapter discusses :term:`view lookup` in detail, and it is the canonical -source for information about views. Technically, :term:`view lookup` is a -:app:`Pyramid` subsystem that is separated from traversal entirely. However, -we'll describe the fundamental behavior of view lookup in the examples in the -next few sections to give you an idea of how traversal and view lookup -cooperate, because they are almost always used together. +We'll also talk a bit about :term:`view lookup`. The +:ref:`view_config_chapter` chapter discusses :term:`view lookup` in +detail, and it is the canonical source for information about views. +Technically, :term:`view lookup` is a :app:`Pyramid` subsystem that is +separated from traversal entirely. However, we'll describe the +fundamental behavior of view lookup in the examples in the next few +sections to give you an idea of how traversal and view lookup cooperate, +because they are almost always used together. .. index:: single: view name @@ -243,8 +236,8 @@ cooperate, because they are almost always used together. A Description of The Traversal Algorithm ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When a user requests a page from your :mod:`traversal` -powered application, -the system uses this algorithm to find a :term:`context` resource and a +When a user requests a page from your traversal-powered application, the +system uses this algorithm to find a :term:`context` resource and a :term:`view name`. #. The request for the page is presented to the :app:`Pyramid` @@ -362,7 +355,7 @@ Here's what happens: - :mod:`traversal` traverses "foo", and attempts to find "bar", which it finds. -- :mod:`traversal` traverses bar, and attempts to find "baz", which it does +- :mod:`traversal` traverses "bar", and attempts to find "baz", which it does not find (the "bar" resource raises a :exc:`KeyError` when asked for "baz"). @@ -469,7 +462,7 @@ References A tutorial showing how :term:`traversal` can be used within a :app:`Pyramid` application exists in :ref:`bfg_wiki_tutorial`. -See the :ref:`views_chapter` chapter for detailed information about +See the :ref:`view_config_chapter` chapter for detailed information about :term:`view lookup`. The :mod:`pyramid.traversal` module contains API functions that deal with diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 76eca454d..ca8fe84f3 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -6,16 +6,28 @@ URL Dispatch ============ -:term:`URL dispatch` provides a simple way to map URLs :term:`view` code -using a simple pattern matching language. An ordered set of patterns is -checked one-by-one. If one of the patterns matches the path information -associated with a request, a particular :term:`view callable` is invoked. If -no route matches, :app:`Pyramid` falls back to trying to use -:term:`traversal` to map the current request to a :term:`view callable`. - -The presence of calls to the :meth:`pyramid.config.Configurator.add_route` -method within your application is a sign that you're using :term:`URL -dispatch`. +:term:`URL dispatch` provides a simple way to map URLs to :term:`view` +code using a simple pattern matching language. An ordered set of +patterns is checked one-by-one. If one of the patterns matches the path +information associated with a request, a particular :term:`view +callable` is invoked. + +:term:`URL dispatch` is one of two ways to perform :term:`resource +location` in :app:`Pyramid`; the other way is using :term:`traversal`. +If no route is matched using :term:`URL dispatch`, :app:`Pyramid` falls +back to :term:`traversal` to handle the :term:`request`. + +It is the responsibility of the :term:`resource location` subsystem +(i.e., :term:`URL dispatch` or :term:`traversal`) to find the resource +object that is the :term:`context` of the :term:`request`. Once the +:term:`context` is determined, :term:`view lookup` is then responsible +for finding and invoking a :term:`view callable`. A view callable is a +specific bit of code, defined in your application, that receives the +:term:`request` and returns a :term:`response` object. + +Where appropriate, we will describe how view lookup interacts with +:term:`resource location`. The :ref:`view_config_chapter` chapter describes +the details of :term:`view lookup`. High-Level Operational Overview ------------------------------- @@ -24,10 +36,9 @@ If route configuration is present in an application, the :app:`Pyramid` :term:`Router` checks every incoming request against an ordered set of URL matching patterns present in a *route map*. -If any route pattern matches the information in the :term:`request` provided -to :app:`Pyramid`, :app:`Pyramid` will shortcut :term:`traversal`, and will -invoke :term:`view lookup` using a :term:`context` resource generated by the -route match. +If any route pattern matches the information in the :term:`request`, +:app:`Pyramid` will invoke :term:`view lookup` using a :term:`context` +resource generated by the route match. However, if no route pattern matches the information in the :term:`request` provided to :app:`Pyramid`, it will fail over to using :term:`traversal` to @@ -72,8 +83,8 @@ example: .. versionchanged:: 1.0a4 Prior to 1.0a4, routes allow for a marker starting with a ``:``, for - example ``/prefix/:one/:two``. Starting in 1.0a4, this style is deprecated - in favor or ``{}`` usage which allows for additional functionality. + example ``/prefix/:one/:two``. This style is now deprecated + in favor of ``{}`` usage which allows for additional functionality. .. index:: single: route configuration; view callable @@ -85,8 +96,7 @@ When a route configuration declaration names a ``view`` attribute, the value of the attribute will reference a :term:`view callable`. This view callable will be invoked when the route matches. A view callable, as described in :ref:`views_chapter`, is developer-supplied code that "does stuff" as the -result of a request. For more information about how to create view -callables, see :ref:`views_chapter`. +result of a request. Here's an example route configuration that references a view callable: @@ -115,49 +125,6 @@ When a route configuration names a ``view`` attribute, the :term:`view callable` named as that ``view`` attribute will always be found and invoked when the associated route pattern matches during a request. -Route View Callable Registration and Lookup Details -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -The purpose of making it possible to specify a view callable within a route -configuration is to prevent developers from needing to deeply understand the -details of :term:`resource location` and :term:`view lookup`. When a route -names a view callable as a ``view`` argument, and a request enters the system -which matches the pattern of the route, the result is simple: the view -callable associated with the route is invoked with the request that caused -the invocation. - -For most usage, you needn't understand more than this; how it works is an -implementation detail. In the interest of completeness, however, we'll -explain how it *does* work in the this section. You can skip it if you're -uninterested. - -When a ``view`` attribute is attached to a route configuration, -:app:`Pyramid` ensures that a :term:`view configuration` is registered that -will always be found when the route pattern is matched during a request. To -do so: - -- A special route-specific :term:`interface` is created at startup time for - each route configuration declaration. - -- When a route configuration declaration mentions a ``view`` attribute, a - :term:`view configuration` is registered at startup time. This view - configuration uses the route-specific interface as a :term:`request` type. - -- At runtime, when a request causes any route to match, the :term:`request` - object is decorated with the route-specific interface. - -- The fact that the request is decorated with a route-specific interface - causes the view lookup machinery to always use the view callable registered - using that interface by the route configuration to service requests that - match the route pattern. - -In this way, we supply a shortcut to the developer. Under the hood, the -:term:`resource location` and :term:`view lookup` subsystems provided by -:app:`Pyramid` are still being utilized, but in a way which does not require -a developer to understand either of them in detail. It also means that we -can allow a developer to combine :term:`URL dispatch` and :term:`traversal` -in various exceptional cases as documented in :ref:`hybrid_chapter`. - .. index:: single: route path pattern syntax @@ -191,10 +158,12 @@ replacement marker (e.g. ``{foo}``) or a certain combination of both. A replacement marker does not need to be preceded by a ``/`` character. A replacement marker is in the format ``{name}``, where this means "accept -any characters up to the next non-alphanumeric character and use this as the -``name`` matchdict value." For example, the following pattern defines one -literal segment ("foo") and two dynamic replacement markers ("baz", and -"bar"): +any characters up to the next slash character and use this as the ``name`` +:term:`matchdict` value." A matchdict is the dictionary representing the +dynamic parts extracted from a URL based on the routing pattern. It is +available as ``request.matchdict``. For example, the following pattern +defines one literal segment (``foo``) and two replacement markers (``baz``, +and ``bar``): .. code-block:: text @@ -236,8 +205,21 @@ To capture both segments, two replacement markers can be used: The literal path ``/foo/biz.html`` will match the above route pattern, and the match result will be ``{'name': 'biz', 'ext': 'html'}``. This occurs -because the replacement marker ``{name}`` has a literal part of ``.`` -(period) between the other replacement marker ``{ext}``. +because there is a literal part of ``.`` (period) between the two replacement +markers ``{name}`` and ``{ext}``. + +Replacement markers can optionally specify a regular expression which will be +used to decide whether a path segment should match the marker. To specify +that a replacement marker should match only a specific set of characters as +defined by a regular expression, you must use a slightly extended form of +replacement marker syntax. Within braces, the replacement marker name must +be followed by a colon, then directly thereafter, the regular expression. +The *default* regular expression associated with a replacement marker +``[^/]+`` matches one or more characters which are not a slash. For example, +under the hood, the replacement marker ``{foo}`` can more verbosely be +spelled as ``{foo:[^/]+}``. You can change this to be an arbitrary regular +expression to match an arbitrary sequence of characters, such as +``{foo:\d+}`` to match only digits. It is possible to use two replacement markers without any literal characters between them, for instance ``/{foo}{bar}``. However, this would be a @@ -251,9 +233,9 @@ replacement marker. For example, for the URL ``/abc/``: - ``/{foo}/`` will match. -Note that values representing path segments matched with a ``{segment}`` -match will be url-unquoted and decoded from UTF-8 into Unicode within the -matchdict. So for instance, the following pattern: +Note that values representing matched path segments will be url-unquoted and +decoded from UTF-8 into Unicode within the matchdict. So for instance, the +following pattern: .. code-block:: text @@ -284,8 +266,11 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text - foo/1/2/ -> {'baz':'1', 'bar':'2', 'fizzle':()} - foo/abc/def/a/b/c -> {'baz':'abc', 'bar':'def', 'fizzle':('a', 'b', 'c')} + foo/1/2/ -> + {'baz':u'1', 'bar':u'2', 'fizzle':()} + + foo/abc/def/a/b/c -> + {'baz':u'abc', 'bar':u'def', 'fizzle':(u'a', u'b', u'c')} Note that when a ``*stararg`` remainder match is matched, the value put into the matchdict is turned into a tuple of path segments representing the @@ -320,8 +305,8 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text - foo/1/2/ -> {'baz':'1', 'bar':'2', 'fizzle':()} - foo/abc/def/a/b/c -> {'baz':'abc', 'bar':'def', 'fizzle': 'a/b/c')} + foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':()} + foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c')} This occurs because the default regular expression for a marker is ``[^/]+`` which will match everything up to the first ``/``, while ``{fizzle:.*}`` will @@ -343,7 +328,7 @@ are added to the application at startup time. This is unlike :term:`traversal`, which depends on emergent behavior which happens as a result of traversing a resource tree. -For routes added via the :mod:`pyramid.config.Configurator.add_route` method, +For routes added via the :mod:`~pyramid.config.Configurator.add_route` method, the order that routes are evaluated is the order in which they are added to the configuration imperatively. @@ -388,7 +373,7 @@ In this way, each route can use a different factory, making it possible to supply a different :term:`context` resource object to the view related to each particular route. -Supplying a different resource factory each route is useful when you're +Supplying a different resource factory for each route is useful when you're trying to use a :app:`Pyramid` :term:`authorization policy` to provide declarative, "context sensitive" security checks; each resource can maintain a separate :term:`ACL`, as documented in @@ -400,12 +385,14 @@ Route Configuration Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Route configuration ``add_route`` statements may specify a large number of -arguments. +arguments. They are documented as part of the API documentation at +:meth:`pyramid.config.Configurator.add_route`. Many of these arguments are :term:`route predicate` arguments. A route predicate argument specifies that some aspect of the request must be true for the associated route to be considered a match during the route matching -process. +process. Examples of route predicate arguments are ``pattern``, ``xhr``, and +``request_method``. Other arguments are view configuration related arguments. These only have an effect when the route configuration names a ``view``. @@ -413,201 +400,13 @@ effect when the route configuration names a ``view``. Other arguments are ``name`` and ``factory``. These arguments represent neither predicates nor view configuration information. -**Non-Predicate Arguments** - -``name`` - The name of the route, e.g. ``myroute``. This attribute is required. It - must be unique among all defined routes in a given application. - -``factory`` - A Python object (often a function or a class) or a :term:`dotted Python - name` to such an object that will generate a :app:`Pyramid` resource object - as the :term:`root` when this route matches. For example, - ``mypackage.resources.MyFactoryClass``. If this argument is not specified, - the traversal root factory will be used. - -``traverse`` - If you would like to cause the :term:`context` resource to be something - other than the :term:`root` resource object when this route matches, you - can spell a traversal pattern as the ``traverse`` argument. This traversal - pattern will be used as the traversal path: traversal will begin at the - root object implied by this route (either the global root, or the object - returned by the ``factory`` associated with this route). - - The syntax of the ``traverse`` argument is the same as it is for - ``pattern``. For example, if the ``pattern`` provided is - ``articles/{article}/edit``, and the ``traverse`` argument provided is - ``/{article}``, when a request comes in that causes the route to match in - such a way that the ``article`` match value is '1' (when the request URI is - ``/articles/1/edit``), the traversal path will be generated as ``/1``. - This means that the root object's ``__getitem__`` will be called with the - name ``1`` during the traversal phase. If the ``1`` object exists, it will - become the :term:`context` resource of the request. - :ref:`traversal_chapter` has more information about traversal. - - If the traversal path contains segment marker names which are not present - in the ``pattern`` argument, a runtime error will occur. The ``traverse`` - pattern should not contain segment markers that do not exist in the - ``pattern``. - - A similar combining of routing and traversal is available when a route is - matched which contains a ``*traverse`` remainder marker in its pattern (see - :ref:`using_traverse_in_a_route_pattern`). The ``traverse`` argument - allows you to associate route patterns with an arbitrary traversal path - without using a a ``*traverse`` remainder marker; instead you can use other - match information. - - Note that the ``traverse`` argument is ignored when attached to a route - that has a ``*traverse`` remainder marker in its pattern. - -**Predicate Arguments** - -``pattern`` - The path of the route e.g. ``ideas/{idea}``. This argument is required. - See :ref:`route_path_pattern_syntax` for information about the syntax of - route paths. If the path doesn't match the current URL, route matching - continues. - - .. note:: In earlier releases of this framework, this argument existed - as ``path``. ``path`` continues to work as an alias for - ``pattern``. - -``xhr`` - This value should be either ``True`` or ``False``. If this value is - specified and is ``True``, the :term:`request` must possess an - ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this route - to match. This is useful for detecting AJAX requests issued from jQuery, - Prototype and other Javascript libraries. If this predicate returns - ``False``, route matching continues. - -``request_method`` - A string representing an HTTP method name, e.g. ``GET``, ``POST``, - ``HEAD``, ``DELETE``, ``PUT``. If this argument is not specified, this - route will match if the request has *any* request method. If this - predicate returns ``False``, route matching continues. - -``path_info`` - This value represents a regular expression pattern that will be tested - against the ``PATH_INFO`` WSGI environment variable. If the regex matches, - this predicate will return ``True``. If this predicate returns ``False``, - route matching continues. - -``request_param`` - This value can be any string. A view declaration with this argument - ensures that the associated route will only match when the request has a - key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` - variable) that has a name which matches the supplied value. If the value - supplied as the argument has a ``=`` sign in it, - e.g. ``request_params="foo=123"``, then the key (``foo``) must both exist - in the ``request.params`` dictionary, and the value must match the right - hand side of the expression (``123``) for the route to "match" the current - request. If this predicate returns ``False``, route matching continues. - -``header`` - This argument represents an HTTP header name or a header name/value pair. - If the argument contains a ``:`` (colon), it will be considered a - name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). If - the value contains a colon, the value portion should be a regular - expression. If the value does not contain a colon, the entire value will - be considered to be the header name (e.g. ``If-Modified-Since``). If the - value evaluates to a header name only without a value, the header specified - by the name must be present in the request for this predicate to be true. - If the value evaluates to a header name/value pair, the header specified by - the name must be present in the request *and* the regular expression - specified as the value must match the header value. Whether or not the - value represents a header name or a header name/value pair, the case of the - header name is not significant. If this predicate returns ``False``, route - matching continues. - -``accept`` - This value represents a match query for one or more mimetypes in the - ``Accept`` HTTP request header. If this value is specified, it must be in - one of the following forms: a mimetype match token in the form - ``text/plain``, a wildcard mimetype match token in the form ``text/*`` or a - match-all wildcard mimetype match token in the form ``*/*``. If any of the - forms matches the ``Accept`` header of the request, this predicate will be - true. If this predicate returns ``False``, route matching continues. - -``custom_predicates`` - This value should be a sequence of references to custom predicate - callables. Use custom predicates when no set of predefined predicates does - what you need. Custom predicates can be combined with predefined - predicates as necessary. Each custom predicate callable should accept two - arguments: ``context`` and ``request`` and should return either ``True`` or - ``False`` after doing arbitrary evaluation of the context resource and/or - the request. If all callables return ``True``, the associated route will - be considered viable for a given request. If any custom predicate returns - ``False``, route matching continues. Note that the value ``context`` will - always be ``None`` when passed to a custom route predicate. - -**View-Related Arguments** - -``view`` - A Python object or a :term:`dotted Python name` to such an object that will - be used as a view callable when this route - matches. e.g. ``mypackage.views.my_view``. - -``view_context`` - A class or an :term:`interface` (or a :term:`dotted Python name` to such an - object) that the :term:`context` resource should possess for the view named - by the route to be used. If this attribute is not specified, the default - (``None``) will be used. - - If the ``view`` argument is not provided, this argument has no effect. - - This attribute can also be spelled as ``for_`` or ``view_for``. - -``view_permission`` - The permission name required to invoke the view associated with this route. - e.g. ``edit``. (see :ref:`using_security_with_urldispatch` for more - information about permissions). - - If the ``view`` attribute is not provided, this argument has no effect. - - This argument can also be spelled as ``permission``. - -``view_renderer`` - This is either a single string term (e.g. ``json``) or a string implying a - path or :term:`asset specification` (e.g. ``templates/views.pt``). If the - renderer value is a single term (does not contain a dot ``.``), the - specified term will be used to look up a renderer implementation, and that - renderer implementation will be used to construct a response from the view - return value. If the renderer term contains a dot (``.``), the specified - term will be treated as a path, and the filename extension of the last - element in the path will be used to look up the renderer implementation, - which will be passed the full path. The renderer implementation will be - used to construct a response from the view return value. See - :ref:`views_which_use_a_renderer` for more information. - - If the ``view`` argument is not provided, this argument has no effect. - - This argument can also be spelled as ``renderer``. - -``view_attr`` - The view machinery defaults to using the ``__call__`` method of the view - callable (or the function itself, if the view callable is a function) to - obtain a response dictionary. The ``attr`` value allows you to vary the - method attribute used to obtain the response. For example, if your view - was a class, and the class has a method named ``index`` and you wanted to - use this method instead of the class' ``__call__`` method to return the - response, you'd say ``attr="index"`` in the view configuration for the - view. This is most useful when the view definition is a class. - - If the ``view`` argument is not provided, this argument has no - effect. - -``use_global_views`` - When a request matches this route, and view lookup cannot find a view which - has a 'route_name' predicate argument that matches the route, try to fall - back to using a view that otherwise matches the context and request. - .. _custom_route_predicates: Custom Route Predicates ~~~~~~~~~~~~~~~~~~~~~~~ Each of the predicate callables fed to the ``custom_predicates`` argument of -:meth:`pyramid.config.Configurator.add_route` must be a callable accepting +:meth:`~pyramid.config.Configurator.add_route` must be a callable accepting two arguments. The first argument passed to a custom predicate is a dictionary conventionally named ``info``. The second argument is the current :term:`request` object. @@ -631,7 +430,7 @@ For example: num_one_two_or_three = any_of('num', 'one', 'two', 'three') - config.add_route('num', '/{num}', + config.add_route('route_to_num', '/{num}', custom_predicates=(num_one_two_or_three,)) The above ``any_of`` function generates a predicate which ensures that the @@ -641,7 +440,7 @@ predicate function named ``num_one_two_or_three``, which ensures that the ``num`` segment is one of the values ``one``, ``two``, or ``three`` , and use the result as a custom predicate by feeding it inside a tuple to the ``custom_predicates`` argument to -:meth:`pyramid.config.Configurator.add_route`. +:meth:`~pyramid.config.Configurator.add_route`. A custom route predicate may also *modify* the ``match`` dictionary. For instance, a predicate might do some type conversion of values: @@ -662,7 +461,7 @@ instance, a predicate might do some type conversion of values: ymd_to_int = integers('year', 'month', 'day') - config.add_route('num', '/{year}/{month}/{day}', + config.add_route('ymd', '/{year}/{month}/{day}', custom_predicates=(ymd_to_int,)) Note that a conversion predicate is still a predicate so it must return @@ -685,7 +484,7 @@ expressions specifying requirements for that marker. For instance: ymd_to_int = integers('year', 'month', 'day') - config.add_route('num', '/{year:\d+}/{month:\d+}/{day:\d+}', + config.add_route('ymd', '/{year:\d+}/{month:\d+}/{day:\d+}', custom_predicates=(ymd_to_int,)) Now the try/except is no longer needed because the route will not match at @@ -795,7 +594,7 @@ The Matched Route When the URL pattern associated with a particular route configuration is matched by a request, an object named ``matched_route`` is added as an attribute of the :term:`request` object. Thus, ``request.matched_route`` -will be an object implementing the :class:`pyramid.interfaces.IRoute` +will be an object implementing the :class:`~pyramid.interfaces.IRoute` interface which matched the request. The most useful attribute of the route object is ``name``, which is the name of the route that matched. @@ -854,7 +653,8 @@ The ``mypackage.views`` module referred to above might look like so: The view has access to the matchdict directly via the request, and can access variables within it that match keys present as a result of the route pattern. -See :ref:`views_chapter` for more information about views. +See :ref:`views_chapter`, and :ref:`view_config_chapter` for more +information about views. Example 2 ~~~~~~~~~ @@ -907,7 +707,7 @@ the :term:`root factory` configured at startup time (the ``root_factory`` argument to the :term:`Configurator` used to configure the application). You can override this behavior by passing in a ``factory`` argument to the -:meth:`pyramid.config.Configurator.add_route` method for a particular route. +:meth:`~pyramid.config.Configurator.add_route` method for a particular route. The ``factory`` should be a callable that accepts a :term:`request` and returns an instance of a class that will be the context resource used by the view. @@ -972,7 +772,7 @@ Matching the Root URL It's not entirely obvious how to use a route pattern to match the root URL ("/"). To do so, give the empty string as a pattern in a call to -:meth:`pyramid.config.Configurator.add_route`: +:meth:`~pyramid.config.Configurator.add_route`: .. code-block:: python :linenos: @@ -1005,8 +805,9 @@ route patterns. For example, if you've configured a route with the ``name`` url = route_url('foo', request, a='1', b='2', c='3') This would return something like the string ``http://example.com/1/2/3`` (at -least if the current protocol and hostname implied ``http:/example.com``). -See the :func:`pyramid.url.route_url` API documentation for more information. +least if the current protocol and hostname implied ``http://example.com``). +See the :func:`~pyramid.url.route_url` API documentation for more +information. .. index:: single: redirecting to slash-appended routes @@ -1033,8 +834,10 @@ route configuration looks like so: .. code-block:: python :linenos: - config.add_route('noslash', 'no_slash', view='myproject.views.no_slash') - config.add_route('hasslash', 'has_slash/', view='myproject.views.has_slash') + config.add_route('noslash', 'no_slash', + view='myproject.views.no_slash') + config.add_route('hasslash', 'has_slash/', + view='myproject.views.has_slash') If a request enters the application with the ``PATH_INFO`` value of ``/has_slash/``, the second route will match. If a request enters the @@ -1109,16 +912,7 @@ Cleaning Up After a Request --------------------------- Sometimes it's required that some cleanup be performed at the end of a -request when a database connection is involved. When :term:`traversal` is -used, this cleanup is often done as a side effect of the traversal -:term:`root factory`. Often the root factory will insert an object into the -WSGI environment that performs some cleanup when its ``__del__`` method is -called. When URL dispatch is used, however, no special root factory is -required, so sometimes that option is not open to you. - -Instead of putting this cleanup logic in the root factory, however, you can -cause a subscriber to be fired when a new request is detected; the subscriber -can do this work. +request when a database connection is involved. For example, let's say you have a ``mypackage`` :app:`Pyramid` application package that uses SQLAlchemy, and you'd like the current SQLAlchemy database @@ -1129,37 +923,32 @@ session to be removed after each request. Put the following in the .. code-block:: python :linenos: - from mypackage.sql import DBSession - - class Cleanup: - def __init__(self, cleaner): - self.cleaner = cleaner - def __del__(self): - self.cleaner() - - def handle_teardown(event): - environ = event.request.environ - environ['mypackage.sqlcleaner'] = Cleanup(DBSession.remove) + from mypackage.models import DBSession -Then add an event subscriber in your startup configuration: + from pyramid.events import subscriber + from pyramid.events import NewRequest -.. code-block:: python - :linenos: + def cleanup_callback(request): + DBSession.remove() - config.add_subscriber('mypackage.handle_teardown', - 'pyramid.events.NewRequest') + @subscriber(NewRequest) + def add_cleanup_callback(event): + event.request.add_finished_callback(cleanup_callback) -Registering a handle_teardown subscriber will cause the DBSession to be -removed whenever the WSGI environment is destroyed (usually at the end of -every request). +Registering the ``cleanup_callback`` finished callback at the start of a +request (by causing the ``add_cleanup_callback`` to receive a +:class:`pyramid.events.NewRequest` event at the start of each request) will +cause the DBSession to be removed whenever request processing has ended. +Note that in the example above, for the :class:`pyramid.events.subscriber` +decorator to "work", the :meth:`pyramid.config.Configurator.scan` method must +be called against your ``mypackage`` package during application +initialization. -.. note:: This is only an example. In particular, it is not necessary - to cause ``DBSession.remove`` to be called as the result of an - event listener in an application generated from any - :app:`Pyramid` paster template, because these all use the - ``repoze.tm2`` middleware. The cleanup done by - ``DBSession.remove`` is unnecessary when ``repoze.tm2`` middleware - is in the WSGI pipeline. +.. note:: This is only an example. In particular, it is not necessary to + cause ``DBSession.remove`` to be called in an application generated from + any :app:`Pyramid` paster template, because these all use the + ``repoze.tm2`` middleware. The cleanup done by ``DBSession.remove`` is + unnecessary when ``repoze.tm2`` middleware is in the WSGI pipeline. .. index:: pair: URL dispatch; security @@ -1231,12 +1020,92 @@ which you started the application from. For example: See :ref:`environment_chapter` for more information about how, and where to set these values. +.. index:: + pair: routes; printing + single: paster proutes + +.. _displaying_application_routes: + +Displaying All Application Routes +--------------------------------- + +You can use the ``paster proutes`` command in a terminal window to print a +summary of routes related to your application. Much like the ``paster +pshell`` command (see :ref:`interactive_shell`), the ``paster proutes`` +command accepts two arguments. The first argument to ``proutes`` is the path +to your application's ``.ini`` file. The second is the ``app`` section name +inside the ``.ini`` file which points to your application. + +For example: + +.. code-block:: text + :linenos: + + [chrism@thinko MyProject]$ ../bin/paster proutes development.ini MyProject + Name Pattern View + ---- ------- ---- + home / <function my_view> + home2 / <function my_view> + another /another None + static/ static/*subpath <static_view object> + catchall /*subpath <function static_view> + +``paster proutes`` generates a table. The table has three columns: a Name +name column, a Pattern column, and a View column. The items listed in the +Name column are route names, the items listen in the Pattern column are route +patterns, and the items listed in the View column are representations of the +view callable that will be invoked when a request matches the associated +route pattern. The view column may show ``None`` if no associated view +callable could be found. If no routes are configured within your +application, nothing will be printed to the console when ``paster proutes`` +is executed. + +Route View Callable Registration and Lookup Details +--------------------------------------------------- + +The purpose of making it possible to specify a view callable within a route +configuration is to prevent developers from needing to deeply understand the +details of :term:`resource location` and :term:`view lookup`. When a route +names a view callable as a ``view`` argument, and a request enters the system +which matches the pattern of the route, the result is simple: the view +callable associated with the route is invoked with the request that caused +the invocation. + +For most usage, you needn't understand more than this; how it works is an +implementation detail. In the interest of completeness, however, we'll +explain how it *does* work in the this section. You can skip it if you're +uninterested. + +When a ``view`` attribute is attached to a route configuration, +:app:`Pyramid` ensures that a :term:`view configuration` is registered that +will always be found when the route pattern is matched during a request. To +do so: + +- A special route-specific :term:`interface` is created at startup time for + each route configuration declaration. + +- When a route configuration declaration mentions a ``view`` attribute, a + :term:`view configuration` is registered at startup time. This view + configuration uses the route-specific interface as a :term:`request` type. + +- At runtime, when a request causes any route to match, the :term:`request` + object is decorated with the route-specific interface. + +- The fact that the request is decorated with a route-specific interface + causes the view lookup machinery to always use the view callable registered + using that interface by the route configuration to service requests that + match the route pattern. + +In this way, we supply a shortcut to the developer. Under the hood, the +:term:`resource location` and :term:`view lookup` subsystems provided by +:app:`Pyramid` are still being utilized, but in a way which does not require +a developer to understand either of them in detail. It also means that we +can allow a developer to combine :term:`URL dispatch` and :term:`traversal` +in various exceptional cases as documented in :ref:`hybrid_chapter`. + References ---------- A tutorial showing how :term:`URL dispatch` can be used to create a :app:`Pyramid` application exists in :ref:`bfg_sql_wiki_tutorial`. -Route configuration may also be added to the system via :term:`ZCML` (see -:ref:`zcml_route_configuration`). - diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst new file mode 100644 index 000000000..9b2500a2b --- /dev/null +++ b/docs/narr/viewconfig.rst @@ -0,0 +1,733 @@ +.. _view_config_chapter: + +.. _view_configuration: + +View Configuration +================== + +.. index:: + single: view lookup + +:term:`View configuration` controls how :term:`view lookup` operates in +your application. In earlier chapters, you have been exposed to a few +simple view configuration declarations without much explanation. In this +chapter we will explore the subject in detail. + +.. _view_lookup: + +View Lookup and Invocation +-------------------------- + +:term:`View lookup` is the :app:`Pyramid` subsystem responsible for finding +an invoking a :term:`view callable`. The view lookup subsystem is passed a +:term:`context` and a :term:`request` object. + +:term:`View configuration` information stored within in the +:term:`application registry` is compared against the context and request by +the view lookup subsystem in order to find the "best" view callable for the +set of circumstances implied by the context and request. + +:term:`View predicate` attributes are an important part of view +configuration that enables the :term:`View lookup` subsystem to find and +invoke the appropriate view. Predicate attributes can be thought of +like "narrowers". In general, the greater number of predicate +attributes possessed by a view's configuration, the more specific the +circumstances need to be before the registered view callable will be +invoked. + +Mapping a Resource or URL Pattern to a View Callable +---------------------------------------------------- + +A developer makes a :term:`view callable` available for use within a +:app:`Pyramid` application via :term:`view configuration`. A view +configuration associates a view callable with a set of statements that +determine the set of circumstances which must be true for the view callable +to be invoked. + +A view configuration statement is made about information present in the +:term:`context` resource and the :term:`request`. + +View configuration is performed in one of these ways: + +- by running a :term:`scan` against application source code which has a + :class:`pyramid.view.view_config` decorator attached to a Python object as + per :ref:`mapping_views_using_a_decorator_section`. + +- by using the :meth:`pyramid.config.Configurator.add_view` method as per + :ref:`mapping_views_using_imperative_config_section`. + +- By specifying a view within a :term:`route configuration`. View + configuration via a route configuration is performed by using the + :meth:`pyramid.config.Configurator.add_route` method, passing a ``view`` + argument specifying a view callable. + +.. note:: A package named ``pyramid_handlers`` (available from PyPI) provides + an analogue of :term:`Pylons` -style "controllers", which are a special + kind of view class which provides more automation when your application + uses :term:`URL dispatch` solely. + +.. _view_configuration_parameters: + +View Configuration Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All forms of view configuration accept the same general types of arguments. + +Many arguments supplied during view configuration are :term:`view predicate` +arguments. View predicate arguments used during view configuration are used +to narrow the set of circumstances in which :term:`view lookup` will find a +particular view callable. + +In general, the fewer number of predicates which are supplied to a +particular view configuration, the more likely it is that the associated +view callable will be invoked. The greater the number supplied, the +less likely. A view with five predicates will always be found and +evaluated before a view with two, for example. All predicates must +match for the associated view to be called. + +This does not mean however, that :app:`Pyramid` "stops looking" when it +finds a view registration with predicates that don't match. If one set +of view predicates does not match, the "next most specific" view (if +any) is consulted for predicates, and so on, until a view is found, or +no view can be matched up with the request. The first view with a set +of predicates all of which match the request environment will be +invoked. + +If no view can be found with predicates which allow it to be matched up with +the request, :app:`Pyramid` will return an error to the user's browser, +representing a "not found" (404) page. See :ref:`changing_the_notfound_view` +for more information about changing the default notfound view. + +Some view configuration arguments are non-predicate arguments. These tend to +modify the response of the view callable or prevent the view callable from +being invoked due to an authorization policy. The presence of non-predicate +arguments in a view configuration does not narrow the circumstances in which +the view callable will be invoked. + +Non-Predicate Arguments ++++++++++++++++++++++++ + +``permission`` + The name of a :term:`permission` that the user must possess in order to + invoke the :term:`view callable`. See :ref:`view_security_section` for + more information about view security and permissions. + + If ``permission`` is not supplied, no permission is registered for this + view (it's accessible by any caller). + +``attr`` + The view machinery defaults to using the ``__call__`` method of the + :term:`view callable` (or the function itself, if the view callable is a + function) to obtain a response. The ``attr`` value allows you to vary the + method attribute used to obtain the response. For example, if your view + was a class, and the class has a method named ``index`` and you wanted to + use this method instead of the class' ``__call__`` method to return the + response, you'd say ``attr="index"`` in the view configuration for the + view. This is most useful when the view definition is a class. + + If ``attr`` is not supplied, ``None`` is used (implying the function itself + if the view is a function, or the ``__call__`` callable attribute if the + view is a class). + +``renderer`` + Denotes the :term:`renderer` implementation which will be used to construct + a :term:`response` from the associated view callable's return value. (see + also :ref:`renderers_chapter`). + + This is either a single string term (e.g. ``json``) or a string implying a + path or :term:`asset specification` (e.g. ``templates/views.pt``) naming a + :term:`renderer` implementation. If the ``renderer`` value does not + contain a dot (``.``), the specified string will be used to look up a + renderer implementation, and that renderer implementation will be used to + construct a response from the view return value. If the ``renderer`` value + contains a dot (``.``), the specified term will be treated as a path, and + the filename extension of the last element in the path will be used to look + up the renderer implementation, which will be passed the full path. + + When the renderer is a path, although a path is usually just a simple + relative pathname (e.g. ``templates/foo.pt``, implying that a template + named "foo.pt" is in the "templates" directory relative to the directory of + the current :term:`package`), a path can be absolute, starting with a slash + on UNIX or a drive letter prefix on Windows. The path can alternately be a + :term:`asset specification` in the form + ``some.dotted.package_name:relative/path``, making it possible to address + template assets which live in a separate package. + + The ``renderer`` attribute is optional. If it is not defined, the "null" + renderer is assumed (no rendering is performed and the value is passed back + to the upstream :app:`Pyramid` machinery unchanged). Note that if the + view callable itself returns a :term:`response` (see :ref:`the_response`), + the specified renderer implementation is never called. + +``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`` + attribute of its own :term:`request`, and the :term:`response` returned by + this view as the ``request.wrapped_response`` attribute of its own request. + Using a wrapper makes it possible to "chain" views together to form a + composite response. The response of the outermost wrapper view will be + returned to the user. The wrapper view will be found as any view is found: + see :ref:`view_lookup`. The "best" wrapper view will be found based on the + lookup ordering: "under the hood" this wrapper view is looked up via + ``pyramid.view.render_view_to_response(context, request, + 'wrapper_viewname')``. The context and request of a wrapper view is the + same context and request of the inner view. + + If ``wrapper`` is not supplied, no wrapper view is used. + +``decorator`` + A :term:`dotted Python name` to a function (or the function itself) which + will be used to decorate the registered :term:`view callable`. The + decorator function will be called with the view callable as a single + argument. The view callable it is passed will accept ``(context, + request)``. The decorator must return a replacement view callable which + also accepts ``(context, request)``. + +``mapper`` + A Python object or :term:`dotted Python name` which refers to a :term:`view + mapper`, or ``None``. By default it is ``None``, which indicates that the + view should use the default view mapper. This plug-point is useful for + Pyramid extension developers, but it's not very useful for 'civilians' who + are just developing stock Pyramid applications. Pay no attention to the man + behind the curtain. + +Predicate Arguments ++++++++++++++++++++ + +These arguments modify view lookup behavior. In general, the more predicate +arguments that are supplied, the more specific, and narrower the usage of the +configured view. + +``name`` + The :term:`view name` required to match this view callable. Read + :ref:`traversal_chapter` to understand the concept of a view name. + + If ``name`` is not supplied, the empty string is used (implying the default + view). + +``context`` + An object representing a Python class that the :term:`context` resource + must be an instance of *or* the :term:`interface` that the :term:`context` + resource must provide in order for this view to be found and called. This + predicate is true when the :term:`context` resource is an instance of the + represented class or if the :term:`context` resource provides the + represented interface; it is otherwise false. + + If ``context`` is not supplied, the value ``None``, which matches any + resource, is used. + +``route_name`` + If ``route_name`` is supplied, the view callable will be invoked only when + the named route has matched. + + This value must match the ``name`` of a :term:`route configuration` + declaration (see :ref:`urldispatch_chapter`) that must match before this + view will be called. Note that the ``route`` configuration referred to by + ``route_name`` will usually have a ``*traverse`` token in the value of its + ``pattern``, representing a part of the path that will be used by + :term:`traversal` against the result of the route's :term:`root factory`. + + If ``route_name`` is not supplied, the view callable will be have a chance + of being invoked if no other route was matched. This is when the + request/context pair found via :term:`resource location` does not indicate + it matched any configured route. + +``request_type`` + This value should be an :term:`interface` that the :term:`request` must + provide in order for this view to be found and called. + + If ``request_type`` is not supplied, the value ``None`` is used, implying + any request type. + + *This is an advanced feature, not often used by "civilians"*. + +``request_method`` + This value can be one of the strings ``GET``, ``POST``, ``PUT``, + ``DELETE``, or ``HEAD`` representing an HTTP ``REQUEST_METHOD``. A view + declaration with this argument ensures that the view will only be called + when the request's ``method`` attribute (aka the ``REQUEST_METHOD`` of the + WSGI environment) string matches the supplied value. + + If ``request_method`` is not supplied, the view will be invoked regardless + of the ``REQUEST_METHOD`` of the :term:`WSGI` environment. + +``request_param`` + This value can be any string. A view declaration with this argument + ensures that the view will only be called when the :term:`request` has a + key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` + variable) that has a name which matches the supplied value. + + If the value supplied has a ``=`` sign in it, + e.g. ``request_param="foo=123"``, then the key (``foo``) must both exist + in the ``request.params`` dictionary, *and* the value must match the right + hand side of the expression (``123``) for the view to "match" the current + request. + + If ``request_param`` is not supplied, the view will be invoked without + consideration of keys and values in the ``request.params`` dictionary. + +``containment`` + This value should be a reference to a Python class or :term:`interface` + that a parent object in the context resource's :term:`lineage` must provide + in order for this view to be found and called. The resources in your + resource tree must be "location-aware" to use this feature. + + If ``containment`` is not supplied, the interfaces and classes in the + lineage are not considered when deciding whether or not to invoke the view + callable. + + See :ref:`location_aware` for more information about location-awareness. + +``xhr`` + This value should be either ``True`` or ``False``. If this value is + specified and is ``True``, the :term:`WSGI` environment must possess an + ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header that has the + value ``XMLHttpRequest`` for the associated view callable to be found and + called. This is useful for detecting AJAX requests issued from jQuery, + Prototype and other Javascript libraries. + + If ``xhr`` is not specified, the ``HTTP_X_REQUESTED_WITH`` HTTP header is + not taken into consideration when deciding whether or not to invoke the + associated view callable. + +``accept`` + The value of this argument represents a match query for one or more + mimetypes in the ``Accept`` HTTP request header. If this value is + specified, it must be in one of the following forms: a mimetype match token + in the form ``text/plain``, a wildcard mimetype match token in the form + ``text/*`` or a match-all wildcard mimetype match token in the form + ``*/*``. If any of the forms matches the ``Accept`` header of the request, + this predicate will be true. + + If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is not + taken into consideration when deciding whether or not to invoke the + associated view callable. + +``header`` + This value represents an HTTP header name or a header name/value pair. + + If ``header`` is specified, it must be a header name or a + ``headername:headervalue`` pair. + + If ``header`` is specified without a value (a bare header name only, + e.g. ``If-Modified-Since``), the view will only be invoked if the HTTP + header exists with any value in the request. + + If ``header`` is specified, and possesses a name/value pair + (e.g. ``User-Agent:Mozilla/.*``), the view will only be invoked if the HTTP + header exists *and* the HTTP header matches the value requested. When the + ``headervalue`` contains a ``:`` (colon), it will be considered a + name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). + The value portion should be a regular expression. + + Whether or not the value represents a header name or a header name/value + pair, the case of the header name is not significant. + + If ``header`` is not specified, the composition, presence or absence of + HTTP headers is not taken into consideration when deciding whether or not + to invoke the associated view callable. + +``path_info`` + This value represents a regular expression pattern that will be tested + against the ``PATH_INFO`` WSGI environment variable to decide whether or + not to call the associated view callable. If the regex matches, this + predicate will be ``True``. + + If ``path_info`` is not specified, the WSGI ``PATH_INFO`` is not taken into + consideration when deciding whether or not to invoke the associated view + callable. + +``custom_predicates`` + If ``custom_predicates`` is specified, it must be a sequence of references + to custom predicate callables. Use custom predicates when no set of + predefined predicates do what you need. Custom predicates can be combined + with predefined predicates as necessary. Each custom predicate callable + should accept two arguments: ``context`` and ``request`` and should return + either ``True`` or ``False`` after doing arbitrary evaluation of the + context resource and/or the request. If all callables return ``True``, the + associated view callable will be considered viable for a given request. + + If ``custom_predicates`` is not specified, no custom predicates are + used. + +.. index:: + single: view_config decorator + +.. _mapping_views_using_a_decorator_section: + +View Configuration Using the ``@view_config`` Decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For better locality of reference, you may use the +:class:`pyramid.view.view_config` decorator to associate your view functions +with URLs instead of using imperative configuration for the same purpose. + +.. warning:: + + Using this feature tends to slows down application startup slightly, as + more work is performed at application startup to scan for view + declarations. + +Usage of the ``view_config`` decorator is a form of :term:`declarative +configuration` in decorator form. :class:`~pyramid.view.view_config` can be +used to associate :term:`view configuration` information -- as done via the +equivalent imperative code -- with a function that acts as a :app:`Pyramid` +view callable. All arguments to the +:meth:`pyramid.config.Configurator.add_view` method (save for the ``view`` +argument) are available in decorator form and mean precisely the same thing. + +An example of the :class:`~pyramid.view.view_config` decorator might reside in +a :app:`Pyramid` application module ``views.py``: + +.. ignore-next-block +.. code-block:: python + :linenos: + + from resources import MyResource + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(name='my_view', request_method='POST', context=MyResource, + permission='read') + def my_view(request): + return Response('OK') + +Using this decorator as above replaces the need to add this imperative +configuration stanza: + +.. ignore-next-block +.. code-block:: python + :linenos: + + config.add_view('mypackage.views.my_view', name='my_view', request_method='POST', + context=MyResource, permission='read') + +All arguments to ``view_config`` may be omitted. For example: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + @view_config() + def my_view(request): + """ My view """ + return Response() + +Such a registration as the one directly above implies that the view name will +be ``my_view``, registered with a ``context`` argument that matches any +resource type, using no permission, registered against requests with any +request method, request type, request param, route name, or containment. + +The mere existence of a ``@view_config`` decorator doesn't suffice to perform +view configuration. All that the decorator does is "annotate" the function +with your configuration declarations, it doesn't process them. To make +:app:`Pyramid` process your :class:`pyramid.view.view_config` declarations, +you *must* use the ``scan`` method of a +:class:`pyramid.config.Configurator`: + +.. code-block:: python + :linenos: + + # config is assumed to be an instance of the + # pyramid.config.Configurator class + config.scan() + +Please see :ref:`decorations_and_code_scanning` for detailed information +about what happens when code is scanned for configuration declarations +resulting from use of decorators like :class:`~pyramid.view.view_config`. + +See :ref:`configuration_module` for additional API arguments to the +:meth:`~pyramid.config.Configurator.scan` method. For example, the method +allows you to supply a ``package`` argument to better control exactly *which* +code will be scanned. + +``@view_config`` Placement +++++++++++++++++++++++++++ + +A :class:`~pyramid.view.view_config` decorator can be placed in various points +in your application. + +If your view callable is a function, it may be used as a function decorator: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(name='edit') + def edit(request): + return Response('edited!') + +If your view callable is a class, the decorator can also be used as a class +decorator in Python 2.6 and better (Python 2.5 and below do not support class +decorators). All the arguments to the decorator are the same when applied +against a class as when they are applied against a function. For example: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + @view_config() + class MyView(object): + def __init__(self, request): + self.request = request + + def __call__(self): + return Response('hello') + +You can use the :class:`~pyramid.view.view_config` decorator as a simple +callable to manually decorate classes in Python 2.5 and below without the +decorator syntactic sugar, if you wish: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + class MyView(object): + def __init__(self, request): + self.request = request + + def __call__(self): + return Response('hello') + + my_view = view_config()(MyView) + +More than one :class:`~pyramid.view.view_config` decorator can be stacked on +top of any number of others. Each decorator creates a separate view +registration. For example: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(name='edit') + @view_config(name='change') + def edit(request): + return Response('edited!') + +This registers the same view under two different names. + +The decorator can also be used against class methods: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + class MyView(object): + def __init__(self, request): + self.request = request + + @view_config(name='hello') + def amethod(self): + return Response('hello') + +When the decorator is used against a class method, a view is registered for +the *class*, so the class constructor must accept an argument list in one of +two forms: either it must accept a single argument ``request`` or it must +accept two arguments, ``context, request``. + +The method which is decorated must return a :term:`response`. + +Using the decorator against a particular method of a class is equivalent to +using the ``attr`` parameter in a decorator attached to the class itself. +For example, the above registration implied by the decorator being used +against the ``amethod`` method could be spelled equivalently as the below: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.view import view_config + + @view_config(attr='amethod', name='hello') + class MyView(object): + def __init__(self, request): + self.request = request + + def amethod(self): + return Response('hello') + +.. index:: + single: add_view + +.. _mapping_views_using_imperative_config_section: + +View Registration Using :meth:`~pyramid.config.Configurator.add_view` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :meth:`pyramid.config.Configurator.add_view` method within +:ref:`configuration_module` is used to configure a view imperatively. The +arguments to this method are very similar to the arguments that you provide +to the ``@view_config`` decorator. For example: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + def hello_world(request): + return Response('hello!') + + # config is assumed to be an instance of the + # pyramid.config.Configurator class + config.add_view(hello_world, name='hello.html') + +The first argument, ``view``, is required. It must either be a Python object +which is the view itself or a :term:`dotted Python name` to such an object. +All other arguments are optional. See +:meth:`pyramid.config.Configurator.add_view` for more information. + +.. index:: + single: resource interfaces + +.. _using_resource_interfaces: + +Using Resource Interfaces In View Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Instead of registering your views with a ``context`` that names a Python +resource *class*, you can optionally register a view callable with a +``context`` which is an :term:`interface`. An interface can be attached +arbitrarily to any resource object. View lookup treats context interfaces +specially, and therefore the identity of a resource can be divorced from that +of the class which implements it. As a result, associating a view with an +interface can provide more flexibility for sharing a single view between two +or more different implementations of a resource type. For example, if two +resource objects of different Python class types share the same interface, +you can use the same view configuration to specify both of them as a +``context``. + +In order to make use of interfaces in your application during view dispatch, +you must create an interface and mark up your resource classes or instances +with interface declarations that refer to this interface. + +To attach an interface to a resource *class*, you define the interface and +use the :func:`zope.interface.implements` function to associate the interface +with the class. + +.. code-block:: python + :linenos: + + from zope.interface import Interface + from zope.interface import implements + + class IHello(Interface): + """ A marker interface """ + + class Hello(object): + implements(IHello) + +To attach an interface to a resource *instance*, you define the interface and +use the :func:`zope.interface.alsoProvides` function to associate the +interface with the instance. This function mutates the instance in such a +way that the interface is attached to it. + +.. code-block:: python + :linenos: + + from zope.interface import Interface + from zope.interface import alsoProvides + + class IHello(Interface): + """ A marker interface """ + + class Hello(object): + pass + + def make_hello(): + hello = Hello() + alsoProvides(hello, IHello) + return hello + +Regardless of how you associate an interface, with a resource instance, or a +resource class, the resulting code to associate that interface with a view +callable is the same. Assuming the above code that defines an ``IHello`` +interface lives in the root of your application, and its module is named +"resources.py", the interface declaration below will associate the +``mypackage.views.hello_world`` view with resources that implement, or +provide, this interface. + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + + config.add_view('mypackage.views.hello_world', name='hello.html', + context='mypackage.resources.IHello') + +Any time a resource that is determined to be the :term:`context` provides +this interface, and a view named ``hello.html`` is looked up against it as +per the URL, the ``mypackage.views.hello_world`` view callable will be +invoked. + +Note, in cases where a view is registered against a resource class, and a +view is also registered against an interface that the resource class +implements, an ambiguity arises. Views registered for the resource class take +precedence over any views registered for any interface the resource class +implements. Thus, if one view configuration names a ``context`` of both the +class type of a resource, and another view configuration names a ``context`` +of interface implemented by the resource's class, and both view +configurations are otherwise identical, the view registered for the context's +class will "win". + +For more information about defining resources with interfaces for use within +view configuration, see :ref:`resources_which_implement_interfaces`. + +.. index:: + single: view security + pair: security; view + +.. _view_security_section: + +Configuring View Security +~~~~~~~~~~~~~~~~~~~~~~~~~ + +If an :term:`authorization policy` is active, any :term:`permission` attached +to a :term:`view configuration` found during view lookup will be verified. +This will ensure that the currently authenticated user possesses that +permission against the :term:`context` resource before the view function is +actually called. Here's an example of specifying a permission in a view +configuration using :meth:`~pyramid.config.Configurator.add_view`: + +.. code-block:: python + :linenos: + + # config is an instance of pyramid.config.Configurator + + config.add_view('myproject.views.add_entry', name='add.html', + context='myproject.resources.IBlog', permission='add') + +When an :term:`authorization policy` is enabled, this view will be protected +with the ``add`` permission. The view will *not be called* if the user does +not possess the ``add`` permission relative to the current :term:`context`. +Instead the :term:`forbidden view` result will be returned to the client as +per :ref:`protecting_views`. + +.. index:: + single: debugging not found errors + single: not found error (debugging) + +.. _debug_notfound_section: + +:exc:`NotFound` Errors +~~~~~~~~~~~~~~~~~~~~~~ + +It's useful to be able to debug :exc:`NotFound` error responses when they +occur unexpectedly due to an application registry misconfiguration. To debug +these errors, use the ``PYRAMID_DEBUG_NOTFOUND`` environment variable or the +``debug_notfound`` configuration file setting. Details of why a view was not +found will be printed to ``stderr``, and the browser representation of the +error will include the same information. See :ref:`environment_chapter` for +more information about how, and where to set these values. + diff --git a/docs/narr/views.rst b/docs/narr/views.rst index ad28e48d4..efbf7924f 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -17,11 +17,12 @@ request made to your application. that implements a view *callable*, and the process of view *lookup*. -The chapter :ref:`resourcelocation_chapter` describes how, using information -from the :term:`request`, a :term:`context` resource is computed. But the -context resource itself isn't very useful without an associated :term:`view -callable`. A view callable returns a response to a user, often using the -context resource to do so. +The :ref:`urldispatch_chapter`, and :ref:`traversal_chapter` chapters +describes how, using information from the :term:`request`, a +:term:`context` resource is computed. But the context resource itself +isn't very useful without an associated :term:`view callable`. A view +callable returns a response to a user, often using the context resource +to do so. The job of actually locating and invoking the "best" :term:`view callable` is the job of the :term:`view lookup` subsystem. The view lookup subsystem @@ -30,31 +31,34 @@ in the :term:`request` against :term:`view configuration` statements made by the developer to choose the most appropriate view callable for a specific set of circumstances. -This chapter provides documentation detailing the process of creating -view callables, documentation about performing view configuration, and -a detailed explanation of view lookup. +This chapter describes how view callables work. In the +:ref:`view_config_chapter` chapter, there are details about performing +view configuration, and a detailed explanation of view lookup. View Callables -------------- -No matter how a view callable is eventually found, all view callables -used by :app:`Pyramid` must be constructed in the same way, and -must return the same kind of return value. - -Most view callables accept a single argument named ``request``. This -argument represents a :app:`Pyramid` :term:`Request` object. A request -object encapsulates a WSGI environment as represented to :app:`Pyramid` by -the upstream :term:`WSGI` server. - -In general, a view callable must return a :mod:`Pyramid` :term:`Response` -object. - -.. note:: The above statement, though it sounds definitive, isn't always - true. See :ref:`renderers_chapter` for information related to using a - :term:`renderer` to convert a non-Response view callable return value into - a Response object. - -View callables can be functions, instances, or classes. +View callables are, at the risk of sounding obvious, callable Python +objects. Specifically, view callables can be functions, classes, or +instances that implement an ``__call__`` method (making the +instance callable). + +View callables must, at a minimum, accept a single argument named +``request``. This argument represents a :app:`Pyramid` :term:`Request` +object. A request object encapsulates a WSGI environment provided to +:app:`Pyramid` by the upstream :term:`WSGI` server. As you might expect, +the request object contains everything your application needs to know +about the specific HTTP request being made. + +A view callable's ultimate responsibility is to create a :mod:`Pyramid` +:term:`Response` object. This can be done by creating the response +object in the view callable code and returning it directly, as we will +be doing in this chapter. However, if a view callable does not return a +response itself, it can be configured to use a :term:`renderer` that +converts its return value into a :term:`Response` object. Using +renderers is the common way that templates are used with view callables +to generate markup. See the :ref:`renderers_chapter` chapter for +details. .. index:: single: view calling convention @@ -63,7 +67,7 @@ View callables can be functions, instances, or classes. .. _function_as_view: Defining a View Callable as a Function -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------------- One of the easiest way to define a view callable is to create a function that accepts a single argument named ``request``, and which returns a @@ -85,12 +89,12 @@ implemented as a function: .. _class_as_view: Defining a View Callable as a Class -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------------- A view callable may also be represented by a Python class instead of a function. When a view callable is a class, the calling semantics are slightly different than when it is a function or another non-class callable. -When a view callable is a class, the class' ``__init__`` is called with a +When a view callable is a class, the class' ``__init__`` method is called with a ``request`` parameter. As a result, an instance of the class is created. Subsequently, that instance's ``__call__`` method is invoked with no parameters. Views defined as classes must have the following traits: @@ -118,16 +122,17 @@ The request object passed to ``__init__`` is the same type of request object described in :ref:`function_as_view`. If you'd like to use a different attribute than ``__call__`` to represent the -method expected to return a response, you can either: - -- use an ``attr`` value as part of the configuration for the view. See - :ref:`view_configuration_parameters`. The same view callable class can be - used in different view configuration statements with different ``attr`` - values, each pointing at a different method of the class if you'd like the - class to represent a collection of related view callables. - -- treat the class as a :term:`view handler` by using it as the ``handler=`` - argument of a call to :meth:`pyramid.config.Configurator.add_handler`. +method expected to return a response, you can use an ``attr`` value as part +of the configuration for the view. See :ref:`view_configuration_parameters`. +The same view callable class can be used in different view configuration +statements with different ``attr`` values, each pointing at a different +method of the class if you'd like the class to represent a collection of +related view callables. + +.. note:: A package named :term:`pyramid_handlers` (available from PyPI) + provides an analogue of :term:`Pylons` -style "controllers", which are a + special kind of view class which provides more automation when your + application uses :term:`URL dispatch` solely. .. index:: single: view calling convention @@ -135,7 +140,7 @@ method expected to return a response, you can either: .. _request_and_context_view_definitions: Alternate View Callable Argument/Calling Conventions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------------------------------- Usually, view callables are defined to accept only a single argument: ``request``. However, view callables may alternately be defined as classes, @@ -168,7 +173,7 @@ The following types work as view callables in this style: return Response('OK') #. Classes that have an ``__init__`` method that accepts ``context, - request`` and a ``__call__`` which accepts no arguments, e.g.: + request`` and a ``__call__`` method which accepts no arguments, e.g.: .. code-block:: python :linenos: @@ -210,9 +215,9 @@ access to the context via ``request.context``. .. _the_response: View Callable Responses -~~~~~~~~~~~~~~~~~~~~~~~ +----------------------- -A view callable may always return an object that implements the :app:`Pyramid` +A view callable may return an object that implements the :app:`Pyramid` :term:`Response` interface. The easiest way to return something that implements the :term:`Response` interface is to return a :class:`pyramid.response.Response` object instance directly. For example: @@ -225,12 +230,12 @@ implements the :term:`Response` interface is to return a def view(request): return Response('OK') -You don't need to always use :class:`pyramid.response.Response` to represent a -response. :app:`Pyramid` provides a range of different "exception" classes +You don't need to always use :class:`~pyramid.response.Response` to represent +a response. :app:`Pyramid` provides a range of different "exception" classes which can act as response objects too. For example, an instance of the class -:class:`pyramid.httpexceptions.HTTPFound` is also a valid response object (see -:ref:`http_redirect`). A view can actually return any object that has the -following attributes. +:class:`pyramid.httpexceptions.HTTPFound` is also a valid response object +(see :ref:`http_redirect`). A view can actually return any object that has +the following attributes. status The HTTP status code (including the name) for the response as a string. @@ -247,7 +252,7 @@ app_iter world!</body></html>']`` or it can be a file-like object, or any other sort of iterable. -These attributes form the notional "Pyramid Response interface". +These attributes form the structure of the "Pyramid Response interface". .. index:: single: view http redirect @@ -256,7 +261,7 @@ These attributes form the notional "Pyramid Response interface". .. _http_redirect: Using a View Callable to Do an HTTP Redirect -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-------------------------------------------- You can issue an HTTP redirect from within a view by returning a particular kind of response. @@ -285,7 +290,7 @@ Unauthorized``. It is possible, however, in Python 2.5 and above, to configure an *exception view* to catch these exceptions, and return an appropriate - :class:`pyramid.response.Response`. The simplest such view could just + :class:`~pyramid.response.Response`. The simplest such view could just catch and return the original exception. See :ref:`exception_views` for more details. @@ -295,7 +300,7 @@ Unauthorized``. .. _special_exceptions_in_callables: Using Special Exceptions In View Callables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------ Usually when a Python exception is raised within a view callable, :app:`Pyramid` allows the exception to propagate all the way out to the @@ -307,11 +312,11 @@ handled by :app:`Pyramid` itself. These are Both are exception classes which accept a single positional constructor argument: a ``message``. -If :exc:`pyramid.exceptions.NotFound` is raised within view code, the result +If :exc:`~pyramid.exceptions.NotFound` is raised within view code, the result of the :term:`Not Found View` will be returned to the user agent which performed the request. -If :exc:`pyramid.exceptions.Forbidden` is raised within view code, the result +If :exc:`~pyramid.exceptions.Forbidden` is raised within view code, the result of the :term:`Forbidden View` will be returned to the user agent which performed the request. @@ -325,10 +330,10 @@ available to the view which :app:`Pyramid` invokes as .. _exception_views: Exception Views -~~~~~~~~~~~~~~~~ +--------------- -The machinery which allows the special :exc:`pyramid.exceptions.NotFound` and -:exc:`pyramid.exceptions.Forbidden` exceptions to be caught by specialized +The machinery which allows the special :exc:`~pyramid.exceptions.NotFound` and +:exc:`~pyramid.exceptions.Forbidden` exceptions to be caught by specialized views as described in :ref:`special_exceptions_in_callables` can also be used by application developers to convert arbitrary exceptions to responses. @@ -364,7 +369,7 @@ raises a ``hellworld.exceptions.ValidationFailure`` exception: Assuming that a :term:`scan` was run to pick up this view registration, this view callable will be invoked whenever a -``helloworld.exceptions.ValidationError`` is raised by your application's +``helloworld.exceptions.ValidationFailure`` is raised by your application's view code. The same exception raised by a custom root factory or a custom traverser is also caught and hooked. @@ -410,7 +415,7 @@ Exception views can be configured with any view registration mechanism: single: views, forms, and unicode Handling Form Submissions in View Callables (Unicode and Character Set Issues) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------------------------------------------ Most web applications need to accept form submissions from web browsers and various other clients. In :app:`Pyramid`, form submission handling logic is @@ -510,7 +515,7 @@ an error when it can't decode some high-order character encoded in another character set within form data, e.g., when ``request.params['somename']`` is accessed. -If you are using the :class:`pyramid.response.Response` class to generate a +If you are using the :class:`~pyramid.response.Response` class to generate a response, or if you use the ``render_template_*`` templating APIs, the UTF-8 charset is set automatically as the default via the ``Content-Type`` header. If you return a ``Content-Type`` header without an explicit charset, a @@ -524,969 +529,4 @@ using your own response object, you will need to ensure you do this yourself. to Unicode objects implicitly in the :app:`Pyramid` default configuration. The keys are still (byte) strings. -.. index:: - single: view configuration - -.. _view_configuration: - -View Configuration: Mapping a Resource or URL Pattern to a View Callable ------------------------------------------------------------------------- - -A developer makes a :term:`view callable` available for use within a -:app:`Pyramid` application via :term:`view configuration`. A view -configuration associates a view callable with a set of statements that -determine the set of circumstances which must be true for the view callable -to be invoked. - -A view configuration statement is made about information present in the -:term:`context` resource and the :term:`request`. - -View configuration is performed in one of these ways: - -- by running a :term:`scan` against application source code which has a - :class:`pyramid.view.view_config` decorator attached to a Python object as - per :class:`pyramid.view.view_config` and - :ref:`mapping_views_using_a_decorator_section`. - -- by using the :meth:`pyramid.config.Configurator.add_view` method as per - :meth:`pyramid.config.Configurator.add_view` and - :ref:`mapping_views_using_imperative_config_section`. - -- By specifying a view within a :term:`route configuration`. View - configuration via a route configuration is performed by using the - :meth:`pyramid.config.Configurator.add_route` method, passing a ``view`` - argument specifying a view callable. - -- by using the :meth:`pyramid.config.Configurator.add_handler` against a - :term:`view handler` class (useful only for :term:`URL dispatch` - applications). - -.. note:: You can also add view configuration by adding a ``<view>``, - ``<route>`` or ``<handler>`` declaration to :term:`ZCML` used by your - application as per :ref:`mapping_views_using_zcml_section`, - :ref:`view_directive`, :ref:`route_directive` or :ref:`handler_directive`. - -.. _view_configuration_parameters: - -View Configuration Parameters -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -All forms of view configuration accept the same general types of arguments. - -Many arguments supplied during view configuration are :term:`view predicate` -arguments. View predicate arguments used during view configuration are used -to narrow the set of circumstances in which :mod:`view lookup` will find a -particular view callable. In general, the fewer number of predicates which -are supplied to a particular view configuration, the more likely it is that -the associated view callable will be invoked. The greater the number -supplied, the less likely. - -Some view configuration arguments are non-predicate arguments. These tend to -modify the response of the view callable or prevent the view callable from -being invoked due to an authorization policy. The presence of non-predicate -arguments in a view configuration does not narrow the circumstances in which -the view callable will be invoked. - -Non-Predicate Arguments -+++++++++++++++++++++++ - -``permission`` - The name of a :term:`permission` that the user must possess in order to - invoke the :term:`view callable`. See :ref:`view_security_section` for - more information about view security and permissions. - - If ``permission`` is not supplied, no permission is registered for this - view (it's accessible by any caller). - -``attr`` - The view machinery defaults to using the ``__call__`` method of the - :term:`view callable` (or the function itself, if the view callable is a - function) to obtain a response. The ``attr`` value allows you to vary the - method attribute used to obtain the response. For example, if your view - was a class, and the class has a method named ``index`` and you wanted to - use this method instead of the class' ``__call__`` method to return the - response, you'd say ``attr="index"`` in the view configuration for the - view. This is most useful when the view definition is a class. - - If ``attr`` is not supplied, ``None`` is used (implying the function itself - if the view is a function, or the ``__call__`` callable attribute if the - view is a class). - -``renderer`` - Denotes the :term:`renderer` implementation which will be used to construct - a :term:`response` from the associated view callable's return value. (see - also :ref:`renderers_chapter`). - - This is either a single string term (e.g. ``json``) or a string implying a - path or :term:`asset specification` (e.g. ``templates/views.pt``) naming a - :term:`renderer` implementation. If the ``renderer`` value does not - contain a dot (``.``), the specified string will be used to look up a - renderer implementation, and that renderer implementation will be used to - construct a response from the view return value. If the ``renderer`` value - contains a dot (``.``), the specified term will be treated as a path, and - the filename extension of the last element in the path will be used to look - up the renderer implementation, which will be passed the full path. - - When the renderer is a path, although a path is usually just a simple - relative pathname (e.g. ``templates/foo.pt``, implying that a template - named "foo.pt" is in the "templates" directory relative to the directory of - the current :term:`package`), a path can be absolute, starting with a slash - on UNIX or a drive letter prefix on Windows. The path can alternately be a - :term:`asset specification` in the form - ``some.dotted.package_name:relative/path``, making it possible to address - template assets which live in a separate package. - - The ``renderer`` attribute is optional. If it is not defined, the "null" - renderer is assumed (no rendering is performed and the value is passed back - to the upstream :app:`Pyramid` machinery unmolested). Note that if the - view callable itself returns a :term:`response` (see :ref:`the_response`), - the specified renderer implementation is never called. - -``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`` - attribute of its own :term:`request`, and the :term:`response` returned by - this view as the ``request.wrapped_response`` attribute of its own request. - Using a wrapper makes it possible to "chain" views together to form a - composite response. The response of the outermost wrapper view will be - returned to the user. The wrapper view will be found as any view is found: - see :ref:`view_lookup`. The "best" wrapper view will be found based on the - lookup ordering: "under the hood" this wrapper view is looked up via - ``pyramid.view.render_view_to_response(context, request, - 'wrapper_viewname')``. The context and request of a wrapper view is the - same context and request of the inner view. - - If ``wrapper`` is not supplied, no wrapper view is used. - -Predicate Arguments -+++++++++++++++++++ - -These arguments modify view lookup behavior. In general, the more predicate -arguments that are supplied, the more specific, and narrower the usage of the -configured view. - -``name`` - The :term:`view name` required to match this view callable. Read - :ref:`traversal_chapter` to understand the concept of a view name. - - If ``name`` is not supplied, the empty string is used (implying the default - view). - -``context`` - An object representing a Python class that the :term:`context` resource - must be an instance of *or* the :term:`interface` that the :term:`context` - resource must provide in order for this view to be found and called. This - predicate is true when the :term:`context` resource is an instance of the - represented class or if the :term:`context` resource provides the - represented interface; it is otherwise false. - - If ``context`` is not supplied, the value ``None``, which matches any - resource, is used. - -``route_name`` - If ``route_name`` is supplied, the view callable will be invoked only when - the named route has matched. - - This value must match the ``name`` of a :term:`route configuration` - declaration (see :ref:`urldispatch_chapter`) that must match before this - view will be called. Note that the ``route`` configuration referred to by - ``route_name`` will usually have a ``*traverse`` token in the value of its - ``pattern``, representing a part of the path that will be used by - :term:`traversal` against the result of the route's :term:`root factory`. - - If ``route_name`` is not supplied, the view callable will be have a chance - of being invoked if no other route was matched. This is when the - request/context pair found via :term:`resource location` does not indicate - it matched any configured route. - -``request_type`` - This value should be an :term:`interface` that the :term:`request` must - provide in order for this view to be found and called. - - If ``request_type`` is not supplied, the value ``None`` is used, implying - any request type. - - *This is an advanced feature, not often used by "civilians"*. - -``request_method`` - This value can either be one of the strings ``GET``, ``POST``, ``PUT``, - ``DELETE``, or ``HEAD`` representing an HTTP ``REQUEST_METHOD``. A view - declaration with this argument ensures that the view will only be called - when the request's ``method`` attribute (aka the ``REQUEST_METHOD`` of the - WSGI environment) string matches the supplied value. - - If ``request_method`` is not supplied, the view will be invoked regardless - of the ``REQUEST_METHOD`` of the :term:`WSGI` environment. - -``request_param`` - This value can be any string. A view declaration with this argument - ensures that the view will only be called when the :term:`request` has a - key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` - variable) that has a name which matches the supplied value. - - If the value supplied has a ``=`` sign in it, - e.g. ``request_params="foo=123"``, then the key (``foo``) must both exist - in the ``request.params`` dictionary, *and* the value must match the right - hand side of the expression (``123``) for the view to "match" the current - request. - - If ``request_param`` is not supplied, the view will be invoked without - consideration of keys and values in the ``request.params`` dictionary. - -``containment`` - This value should be a reference to a Python class or :term:`interface` - that a parent object in the context resource's :term:`lineage` must provide - in order for this view to be found and called. The resources in your - resource tree must be "location-aware" to use this feature. - - If ``containment`` is not supplied, the interfaces and classes in the - lineage are not considered when deciding whether or not to invoke the view - callable. - - See :ref:`location_aware` for more information about location-awareness. - -``xhr`` - This value should be either ``True`` or ``False``. If this value is - specified and is ``True``, the :term:`WSGI` environment must possess an - ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header that has the - value ``XMLHttpRequest`` for the associated view callable to be found and - called. This is useful for detecting AJAX requests issued from jQuery, - Prototype and other Javascript libraries. - - If ``xhr`` is not specified, the ``HTTP_X_REQUESTED_WITH`` HTTP header is - not taken into consideration when deciding whether or not to invoke the - associated view callable. - -``accept`` - The value of this argument represents a match query for one or more - mimetypes in the ``Accept`` HTTP request header. If this value is - specified, it must be in one of the following forms: a mimetype match token - in the form ``text/plain``, a wildcard mimetype match token in the form - ``text/*`` or a match-all wildcard mimetype match token in the form - ``*/*``. If any of the forms matches the ``Accept`` header of the request, - this predicate will be true. - - If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is not - taken into consideration when deciding whether or not to invoke the - associated view callable. - -``header`` - This value represents an HTTP header name or a header name/value pair. - - If ``header`` is specified, it must be a header name or a - ``headername:headervalue`` pair. - - If ``header`` is specified without a value (a bare header name only, - e.g. ``If-Modified-Since``), the view will only be invoked if the HTTP - header exists with any value in the request. - - If ``header`` is specified, and possesses a name/value pair - (e.g. ``User-Agent:Mozilla/.*``), the view will only be invoked if the HTTP - header exists *and* the HTTP header matches the value requested. When the - ``headervalue`` contains a ``:`` (colon), it will be considered a - name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). - The value portion should be a regular expression. - - Whether or not the value represents a header name or a header name/value - pair, the case of the header name is not significant. - - If ``header`` is not specified, the composition, presence or absence of - HTTP headers is not taken into consideration when deciding whether or not - to invoke the associated view callable. - -``path_info`` - This value represents a regular expression pattern that will be tested - against the ``PATH_INFO`` WSGI environment variable to decide whether or - not to call the associated view callable. If the regex matches, this - predicate will be ``True``. - - If ``path_info`` is not specified, the WSGI ``PATH_INFO`` is not taken into - consideration when deciding whether or not to invoke the associated view - callable. - -``custom_predicates`` - If ``custom_predicates`` is specified, it must be a sequence of references - to custom predicate callables. Use custom predicates when no set of - predefined predicates do what you need. Custom predicates can be combined - with predefined predicates as necessary. Each custom predicate callable - should accept two arguments: ``context`` and ``request`` and should return - either ``True`` or ``False`` after doing arbitrary evaluation of the - context resource and/or the request. If all callables return ``True``, the - associated view callable will be considered viable for a given request. - - If ``custom_predicates`` is not specified, no custom predicates are - used. - -.. index:: - single: view_config decorator - -.. _mapping_views_using_a_decorator_section: - -View Configuration Using the ``@view_config`` Decorator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For better locality of reference, you may use the -:class:`pyramid.view.view_config` decorator to associate your view functions -with URLs instead of using :term:`ZCML` or imperative configuration for the -same purpose. - -.. warning:: - - Using this feature tends to slows down application startup slightly, as - more work is performed at application startup to scan for view - declarations. - -Usage of the ``view_config`` decorator is a form of :term:`declarative -configuration`, like ZCML, but in decorator form. -:class:`pyramid.view.view_config` can be used to associate :term:`view -configuration` information -- as done via the equivalent imperative code or -ZCML -- with a function that acts as a :app:`Pyramid` view callable. All -arguments to the :meth:`pyramid.config.Configurator.add_view` method (save -for the ``view`` argument) are available in decorator form and mean precisely -the same thing. - -An example of the :class:`pyramid.view.view_config` decorator might reside in -a :app:`Pyramid` application module ``views.py``: - -.. ignore-next-block -.. code-block:: python - :linenos: - - from resources import MyResource - from pyramid.view import view_config - from pyramid.response import Response - - @view_config(name='my_view', request_method='POST', context=MyResource, - permission='read') - def my_view(request): - return Response('OK') - -Using this decorator as above replaces the need to add this imperative -configuration stanza: - -.. ignore-next-block -.. code-block:: python - :linenos: - - config.add_view('.views.my_view', name='my_view', request_method='POST', - context=MyResource, permission='read') - -All arguments to ``view_config`` may be omitted. For example: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - @view_config() - def my_view(request): - """ My view """ - return Response() - -Such a registration as the one directly above implies that the view name will -be ``my_view``, registered with a ``context`` argument that matches any -resource type, using no permission, registered against requests with any -request method, request type, request param, route name, or containment. - -The mere existence of a ``@view_config`` decorator doesn't suffice to perform -view configuration. All that the decorator does is "annotate" the function -with your configuration declarations, it doesn't process them. To make -:app:`Pyramid` process your :class:`pyramid.view.view_config` declarations, -you *must* do use the ``scan`` method of a -:class:`pyramid.config.Configurator`: - -.. code-block:: python - :linenos: - - # config is assumed to be an instance of the - # pyramid.config.Configurator class - config.scan() - -.. note:: See :ref:`zcml_scanning` for information about how to invoke a scan - via ZCML (if you're not using imperative configuration). - -Please see :ref:`decorations_and_code_scanning` for detailed information -about what happens when code is scanned for configuration declarations -resulting from use of decorators like :class:`pyramid.view.view_config`. - -See :ref:`configuration_module` for additional API arguments to the -:meth:`pyramid.config.Configurator.scan` method. For example, the method -allows you to supply a ``package`` argument to better control exactly *which* -code will be scanned. - -``@view_config`` Placement -++++++++++++++++++++++++++ - -A :class:`pyramid.view.view_config` decorator can be placed in various points -in your application. - -If your view callable is a function, it may be used as a function decorator: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - from pyramid.response import Response - - @view_config(name='edit') - def edit(request): - return Response('edited!') - -If your view callable is a class, the decorator can also be used as a class -decorator in Python 2.6 and better (Python 2.5 and below do not support class -decorators). All the arguments to the decorator are the same when applied -against a class as when they are applied against a function. For example: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - @view_config() - class MyView(object): - def __init__(self, request): - self.request = request - - def __call__(self): - return Response('hello') - -You can use the :class:`pyramid.view.view_config` decorator as a simple -callable to manually decorate classes in Python 2.5 and below without the -decorator syntactic sugar, if you wish: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - class MyView(object): - def __init__(self, request): - self.request = request - - def __call__(self): - return Response('hello') - - my_view = view_config()(MyView) - -More than one :class:`pyramid.view.view_config` decorator can be stacked on -top of any number of others. Each decorator creates a separate view -registration. For example: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - from pyramid.response import Response - - @view_config(name='edit') - @view_config(name='change') - def edit(request): - return Response('edited!') - -This registers the same view under two different names. - -The decorator can also be used against class methods: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - class MyView(object): - def __init__(self, request): - self.request = request - - @view_config(name='hello') - def amethod(self): - return Response('hello') - -When the decorator is used against a class method, a view is registered for -the *class*, so the class constructor must accept an argument list in one of -two forms: either it must accept a single argument ``request`` or it must -accept two arguments, ``context, request``. - -The method which is decorated must return a :term:`response`. - -Using the decorator against a particular method of a class is equivalent to -using the ``attr`` parameter in a decorator attached to the class itself. -For example, the above registration implied by the decorator being used -against the ``amethod`` method could be spelled equivalently as the below: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - from pyramid.view import view_config - - @view_config(attr='amethod', name='hello') - class MyView(object): - def __init__(self, request): - self.request = request - - def amethod(self): - return Response('hello') - -.. index:: - single: add_view - -.. _mapping_views_using_imperative_config_section: - -View Registration Using :meth:`~pyramid.config.Configurator.add_view` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The :meth:`pyramid.config.Configurator.add_view` method within -:ref:`configuration_module` is used to configure a view imperatively. The -arguments to this method are very similar to the arguments that you provide -to the ``@view_config`` decorator. For example: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - - def hello_world(request): - return Response('hello!') - - # config is assumed to be an instance of the - # pyramid.config.Configurator class - config.add_view(hello_world, name='hello.html') - -The first argument, ``view``, is required. It must either be a Python object -which is the view itself or a :term:`dotted Python name` to such an object. -All other arguments are optional. See -:meth:`pyramid.config.Configurator.add_view` for more information. - -.. _using_add_handler: - -Handler Registration Using :meth:`~pyramid.config.Configurator.add_handler` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:app:`Pyramid` provides the special concept of a :term:`view handler`. View -handlers are view classes that implement a number of methods, each of which -is a :term:`view callable` as a convenience for :term:`URL dispatch` users. - -.. note:: - - View handlers are *not* useful when using :term:`traversal`, only when using - :term:`url dispatch`. - -Using a view handler instead of a plain function or class :term:`view -callable` makes it unnecessary to call -:meth:`pyramid.config.Configurator.add_route` (and/or -:meth:`pyramid.config.Configurator.add_view`) "by hand" multiple times, -making it more pleasant to register a collection of views as a single class -when using :term:`url dispatch`. The view handler machinery also introduces -the concept of an ``action``, which is used as a :term:`view predicate` to -control which method of the handler is called. The method name is the -default *action name* of a handler view callable. - -The concept of a view handler is analogous to a "controller" in Pylons 1.0. - -The view handler class is initialized by :app:`Pyramid` in the same manner as -a "plain" view class. Its ``__init__`` is called with a request object (see -:ref:`class_as_view`). It implements methods, each of which is a :term:`view -callable`. When a request enters the system which corresponds with an -*action* related to one of its view callable methods, this method is called, -and it is expected to return a response. - -Here's an example view handler class: - -.. code-block:: python - :linenos: - - from pyramid.response import Response - - from pyramid.view import action - - class Hello(object): - def __init__(self, request): - self.request = request - - def index(self): - return Response('Hello world!') - - @action(renderer="mytemplate.mak") - def bye(self): - return {} - -The :class:`pyramid.view.action` decorator is used to fine-tune the view -parameters for each potential view callable which is a method of the handler. - -Handlers are added to application configuration via the -:meth:`pyramid.config.Configurator.add_handler` API. The -:meth:`~pyramid.config.Configurator.add_handler` method will scan a -:term:`view handler` class and automatically set up view configurations for -its methods that represent "auto-exposed" view callable, or those that were -decorated explicitly with the :class:`~pyramid.view.action` decorator. This -decorator is used to setup additional view configuration information for -individual methods of the class, and can be used repeatedly for a single view -method to register multiple view configurations for it. - -.. code-block:: python - :linenos: - - from myapp.handlers import Hello - config.add_handler('hello', '/hello/{action}', handler=Hello) - -This example will result in a route being added for the pattern -``/hello/{action}``, and each method of the ``Hello`` class will then be -examined to see if it should be registered as a potential view callable when -the ``/hello/{action}`` pattern matches. The value of ``{action}`` in the -route pattern will be used to determine which view should be called, and each -view in the class will be setup with a view predicate that requires a -specific ``action`` name. By default, the action name for a method of a -handler is the method name. - -If the URL was ``/hello/index``, the above example pattern would match, and, -by default, the ``index`` method of the ``Hello`` class would be called. - -Alternatively, the action can be declared specifically for a URL to be -registered for a *specific* ``action`` name: - -.. code-block:: python - :linenos: - - from myapp.handlers import Hello - config.add_handler('hello_index', '/hello/index', - handler=Hello, action='index') - -This will result one of the methods that are configured for the ``action`` of -'index' in the ``Hello`` handler class to be called. In this case the name of -the method is the same as the action name: ``index``. However, this need not -be the case, as we will see below. - -When calling :meth:`~pyramid.config.Configurator.add_handler`, an ``action`` -is required in either the route pattern or as a keyword argument, but -**cannot appear in both places**. A ``handler`` argument must also be -supplied, which can be either a :term:`asset specification` or a Python -reference to the handler class. Additional keyword arguments are passed -directly through to :meth:`pyramid.config.Configurator.add_route`. - -For example: - -.. code-block:: python - :linenos: - - config.add_handler('hello', '/hello/{action}', - handler='mypackage.handlers.MyHandler') - -Multiple :meth:`~pyramid.config.Configurator.add_handler` calls can specify -the same handler, to register specific route names for different -handler/action combinations. For example: - -.. code-block:: python - :linenos: - - config.add_handler('hello_index', '/hello/index', - handler=Hello, action='index') - config.add_handler('bye_index', '/hello/bye', - handler=Hello, action='bye') - -.. note:: - - Handler configuration may also be added to the system via :term:`ZCML` (see - :ref:`zcml_handler_configuration`). - -View Setup in the Handler Class -+++++++++++++++++++++++++++++++ - -A handler class can have a single class level attribute called -``__autoexpose__`` which should be a regular expression or the value -``None``. It's used to determine which method names will result in additional -view configurations being registered. - -When :meth:`~pyramid.config.Configurator.add_handler` runs, every method in -the handler class will be searched and a view registered if the method name -matches the ``__autoexpose__`` regular expression, or if the method was -decorated with :class:`~pyramid.view.action`. - -Every method in the handler class that has a name meeting the -``__autoexpose__`` regular expression will have a view registered for an -``action`` name corresponding to the method name. This functionality can be -disabled by setting the ``__autoexpose__`` attribute to ``None``: - -.. code-block:: python - :linenos: - - from pyramid.view import action - - class Hello(object): - __autoexpose__ = None - - def __init__(self, request): - self.request = request - - @action() - def index(self): - return Response('Hello world!') - - @action(renderer="mytemplate.mak") - def bye(self): - return {} - -With auto-expose effectively disabled, no views will be registered for a -method unless it is specifically decorated with -:class:`~pyramid.view.action`. - -Action Decorators in a Handler -++++++++++++++++++++++++++++++ - -The :class:`~pyramid.view.action` decorator registers view configuration -information on the handler method, which is used by -:meth:`~pyramid.config.Configurator.add_handler` to setup the view -configuration. - -All keyword arguments are recorded, and passed to -:meth:`~pyramid.config.Configurator.add_view`. Any valid keyword arguments -for :meth:`~pyramid.config.Configurator.add_view` can thus be used with the -:class:`~pyramid.view.action` decorator to further restrict when the view -will be called. - -One important difference is that a handler method can respond to an -``action`` name that is different from the method name by passing in a -``name`` argument. - -Example: - -.. code-block:: python - :linenos: - - from pyramid.view import action - - class Hello(object): - def __init__(self, request): - self.request = request - - @action(name='index', renderer='created.mak', request_method='POST') - def create(self): - return {} - - @action(renderer="view_all.mak", request_method='GET') - def index(self): - return {} - -This will register two views that require the ``action`` to be ``index``, -with the additional view predicate requiring a specific request method. - -It can be useful to decorate a single method multiple times with -:class:`~pyramid.view.action`. Each action decorator will register a new view -for the method. By specifying different names and renderers for each action, -the same view logic can be exposed and rendered differently on multiple URLs. - -Example: - -.. code-block:: python - :linenos: - - from pyramid.view import action - - class Hello(object): - def __init__(self, request): - self.request = request - - @action(name='home', renderer='home.mak') - @action(name='about', renderer='about.mak') - def show_template(self): - # prep some template vars - return {} - - # in the config - config.add_handler('hello', '/hello/{action}', handler=Hello) - -With this configuration, the url ``/hello/home`` will find a view -configuration that results in calling the ``show_template`` method, then -rendering the template with ``home.mak``, and the url ``/hello/about`` will -call the same method and render the ``about.mak`` template. - -.. index:: - single: resource interfaces - -.. _using_resource_interfaces: - -Using Resource Interfaces In View Configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Instead of registering your views with a ``context`` that names a Python -resource *class*, you can optionally register a view callable with a -``context`` which is an :term:`interface`. An interface can be attached -arbitrarily to any resource object. View lookup treats context interfaces -specially, and therefore the identity of a resource can be divorced from that -of the class which implements it. As a result, associating a view with an -interface can provide more flexibility for sharing a single view between two -or more different implementations of a resource type. For example, if two -resource objects of different Python class types share the same interface, -you can use the same view configuration to specify both of them as a -``context``. - -In order to make use of interfaces in your application during view dispatch, -you must create an interface and mark up your resource classes or instances -with interface declarations that refer to this interface. - -To attach an interface to a resource *class*, you define the interface and -use the :func:`zope.interface.implements` function to associate the interface -with the class. - -.. code-block:: python - :linenos: - - from zope.interface import Interface - from zope.interface import implements - - class IHello(Interface): - """ A marker interface """ - - class Hello(object): - implements(IHello) - -To attach an interface to a resource *instance*, you define the interface and -use the :func:`zope.interface.alsoProvides` function to associate the -interface with the instance. This function mutates the instance in such a -way that the interface is attached to it. - -.. code-block:: python - :linenos: - - from zope.interface import Interface - from zope.interface import alsoProvides - - class IHello(Interface): - """ A marker interface """ - - class Hello(object): - pass - - def make_hello(): - hello = Hello() - alsoProvides(hello, IHello) - return hello - -Regardless of how you associate an interface, with a resource instance, or a -resource class, the resulting code to associate that interface with a view -callable is the same. Assuming the above code that defines an ``IHello`` -interface lives in the root of your application, and its module is named -"resources.py", the interface declaration below will associate the -``mypackage.views.hello_world`` view with resources that implement, or -provide, this interface. - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - - config.add_view('mypackage.views.hello_world', name='hello.html', - context='mypackage.resources.IHello') - -Any time a resource that is determined to be the :term:`context` provides -this interface, and a view named ``hello.html`` is looked up against it as -per the URL, the ``mypackage.views.hello_world`` view callable will be -invoked. - -Note, in cases where a view is registered against a resource class, and a -view is also registered against an interface that the resource class -implements, an ambiguity arises. Views registered for the resource class take -precedence over any views registered for any interface the resource class -implements. Thus, if one view configuration names a ``context`` of both the -class type of a resource, and another view configuration names a ``context`` -of interface implemented by the resource's class, and both view -configurations are otherwise identical, the view registered for the context's -class will "win". - -For more information about defining resources with interfaces for use within -view configuration, see :ref:`resources_which_implement_interfaces`. - -.. index:: - single: view security - pair: security; view - -.. _view_security_section: - -Configuring View Security -~~~~~~~~~~~~~~~~~~~~~~~~~ - -If an :term:`authorization policy` is active, any :term:`permission` attached -to a :term:`view configuration` found during view lookup will be verified. -This will ensure that the currently authenticated user possesses that -permission against the :term:`context` resource before the view function is -actually called. Here's an example of specifying a permission in a view -configuration using :meth:`pyramid.config.Configurator.add_view`: - -.. code-block:: python - :linenos: - - # config is an instance of pyramid.config.Configurator - - config.add_view('myproject.views.add_entry', name='add.html', - context='myproject.resources.IBlog', permission='add') - -When an :term:`authorization policy` is enabled, this view will be protected -with the ``add`` permission. The view will *not be called* if the user does -not possess the ``add`` permission relative to the current :term:`context`. -Instead the :term:`forbidden view` result will be returned to the client as -per :ref:`protecting_views`. - -.. index:: - single: view lookup - -.. _view_lookup: - -View Lookup and Invocation --------------------------- - -:term:`View lookup` is the :app:`Pyramid` subsystem responsible for finding -an invoking a :term:`view callable`. The view lookup subsystem is passed a -:term:`context` and a :term:`request` object. - -:term:`View configuration` information stored within in the -:term:`application registry` is compared against the context and request by -the view lookup subsystem in order to find the "best" view callable for the -set of circumstances implied by the context and request. - -Predicate attributes of view configuration can be thought of like -"narrowers". In general, the greater number of predicate attributes -possessed by a view's configuration, the more specific the circumstances need -to be before the registered view callable will be invoked. - -For any given request, a view with five predicates will always be found and -evaluated before a view with two, for example. All predicates must match for -the associated view to be called. - -This does not mean however, that :app:`Pyramid` "stops looking" when it finds -a view registration with predicates that don't match. If one set of view -predicates does not match, the "next most specific" view (if any) view is -consulted for predicates, and so on, until a view is found, or no view can be -matched up with the request. The first view with a set of predicates all of -which match the request environment will be invoked. - -If no view can be found with predicates which allow it to be matched up with -the request, :app:`Pyramid` will return an error to the user's browser, -representing a "not found" (404) page. See :ref:`changing_the_notfound_view` -for more information about changing the default notfound view. - -.. index:: - single: debugging not found errors - single: not found error (debugging) - -.. _debug_notfound_section: - -:exc:`NotFound` Errors -~~~~~~~~~~~~~~~~~~~~~~ - -It's useful to be able to debug :exc:`NotFound` error responses when they -occur unexpectedly due to an application registry misconfiguration. To debug -these errors, use the ``PYRAMID_DEBUG_NOTFOUND`` environment variable or the -``debug_notfound`` configuration file setting. Details of why a view was not -found will be printed to ``stderr``, and the browser representation of the -error will include the same information. See :ref:`environment_chapter` for -more information about how, and where to set these values. - -Further Information -------------------- - -The chapter entitled :ref:`renderers_chapter` explains how to create -functions (or instances/classes) which do not return a :term:`Response` -object, yet which still can be used as view callables. diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 83d096a57..26a40a59b 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -20,9 +20,11 @@ class, which is a subclass of :class:`webob.Request`. The Users can also return an instance of :mod:`webob.Response` directly from a view as necessary. -WebOb is a project separate from :app:`Pyramid` with a separate set -of authors and a fully separate `set of documentation -<http://pythonpaste.org/webob/>`_. +WebOb is a project separate from :app:`Pyramid` with a separate set of +authors and a fully separate `set of documentation +<http://pythonpaste.org/webob/>`_. Pyramid adds some 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 @@ -109,11 +111,11 @@ instance, ``req.if_modified_since`` returns a `datetime Special Attributes Added to the Request by :app:`Pyramid` ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -In addition to the standard :term:`WebOb` attributes, :app:`Pyramid` -adds special attributes to every request: ``context``, ``registry``, -``root``, ``subpath``, ``traversed``, ``view_name``, ``virtual_root``, -``virtual_root_path``, ``session``, and ``tmpl_context``. These -attributes are documented further within the +In addition to the standard :term:`WebOb` attributes, :app:`Pyramid` adds +special attributes to every request: ``context``, ``registry``, ``root``, +``subpath``, ``traversed``, ``view_name``, ``virtual_root``, +``virtual_root_path``, ``session``, and ``tmpl_context``, ``matchdict``, and +``matched_route``. These attributes are documented further within the :class:`pyramid.request.Request` API documentation. .. index:: diff --git a/docs/narr/zca.rst b/docs/narr/zca.rst index f330fd551..fcab0653e 100644 --- a/docs/narr/zca.rst +++ b/docs/narr/zca.rst @@ -57,7 +57,7 @@ Using the ZCA Global API in a :app:`Pyramid` Application ----------------------------------------------------------- :term:`Zope` uses a single ZCA registry -- the "global" ZCA registry --- for all Zope applications run in the same Python process, +-- for all Zope applications that run in the same Python process, effectively making it impossible to run more than one Zope application in a single process. @@ -158,9 +158,7 @@ Consider the following bit of idiomatic :app:`Pyramid` startup code: def app(global_settings, **settings): config = Configurator(settings=settings) - config.begin() - config.load_zcml('configure.zcml') - config.end() + config.include('some.other.package') return config.make_wsgi_app() When the ``app`` function above is run, a :term:`Configurator` is @@ -173,7 +171,7 @@ when a :term:`Configurator` constructor is called, or when a During a request, the application registry created by the Configurator is "made current". This means calls to -:func:`pyramid.threadlocal.get_current_registry` in the thread +:func:`~pyramid.threadlocal.get_current_registry` in the thread handling the request will return the component registry associated with the application. @@ -186,7 +184,7 @@ always return the global ZCA registry (the one in To "fix" this and make the ZCA global APIs use the "current" BFG registry, you need to call -:meth:`pyramid.config.Configurator.hook_zca` within your +:meth:`~pyramid.config.Configurator.hook_zca` within your setup code. For example: .. code-block:: python @@ -198,9 +196,7 @@ setup code. For example: def app(global_settings, **settings): config = Configurator(settings=settings) config.hook_zca() - config.begin() - config.load_zcml('configure.zcml') - config.end() + config.include('some.other.application') return config.make_wsgi_app() We've added a line to our original startup code, line number 6, which @@ -250,9 +246,7 @@ registry at startup time instead of constructing a new one: config = Configurator(registry=globalreg) config.setup_registry(settings=settings) config.hook_zca() - config.begin() - config.load_zcml('configure.zcml') - config.end() + config.include('some.other.application') return config.make_wsgi_app() Lines 5, 6, and 7 above are the interesting ones. Line 5 retrieves @@ -268,36 +262,3 @@ rather than creating a new application-specific registry; since by default the ZCA global API will use this registry, things will work as you might expect a Zope app to when you use the global ZCA API. -.. index:: - single: Zope ZCML directives - single: getGlobalSiteManager - single: getSiteManager - -Using Broken ZCML Directives ----------------------------- - -Some :term:`Zope` and third-party :term:`ZCML` directives use the -``zope.component.getGlobalSiteManager`` API to get "the registry" when -they should actually be calling ``zope.component.getSiteManager``. - -``zope.component.getSiteManager`` can be overridden by -:app:`Pyramid` via -:meth:`pyramid.config.Configurator.hook_zca`, while -``zope.component.getGlobalSiteManager`` cannot. Directives that use -``zope.component.getGlobalSiteManager`` are effectively broken; no -ZCML directive should be using this function to find a registry to -populate. - -You cannot use ZCML directives which use -``zope.component.getGlobalSiteManager`` within a :app:`Pyramid` -application without passing the ZCA global registry to the -:term:`Configurator` constructor at application startup, as per -:ref:`using_the_zca_global_registry`. - -One alternative exists: fix the ZCML directive to use -``getSiteManager`` rather than ``getGlobalSiteManager``. If a -directive disuses ``getGlobalSiteManager``, the ``hook_zca`` method of -using a component registry as documented in :ref:`hook_zca` will begin -to work, allowing you to make use of the ZCML directive without -also using the ZCA global registry. - diff --git a/docs/tutorials/bfg/index.rst b/docs/tutorials/bfg/index.rst index 3c0b3bf01..9f9a5238c 100644 --- a/docs/tutorials/bfg/index.rst +++ b/docs/tutorials/bfg/index.rst @@ -176,6 +176,10 @@ Here's how to convert a :mod:`repoze.bfg` application to a <bfg:failingtag attr="foo"/> </configure> + You will also need to add the ``pyramid_zcml`` package to your + ``setup.py`` ``install_requires`` list. In Pyramid, ZCML configuration + became an optional add-on supported by the ``pyramid_zcml`` package. + #. Retest your application using :app:`Pyramid`. This might be as easy as: diff --git a/docs/tutorials/catalog/index.rst b/docs/tutorials/catalog/index.rst deleted file mode 100644 index e4e5bd720..000000000 --- a/docs/tutorials/catalog/index.rst +++ /dev/null @@ -1,127 +0,0 @@ -.. _catalog_tutorial: - -Using :mod:`repoze.catalog` Within :app:`Pyramid` -================================================= - -:mod:`repoze.catalog` is a ZODB-based system that can be used to index -Python objects. It also offers a query interface for retrieving -previously indexed data. Those whom are used to Zope's "ZCatalog" -implementation will feel at home using :mod:`repoze.catalog`. - -This tutorial assumes that you want a Zope-like setup. For example, -it assumes you want to use a persistent ZODB object as your -:term:`root` object, and that the :mod:`repoze.catalog` catalog will -be an attribute of this root object. It is further assumed that you -want the application to be based on :term:`traversal`. - -#. Follow the :ref:`zodb_with_zeo` tutorial to get a system set up - with ZODB and ZEO. When you are finished, come back here. - -#. Install the :mod:`repoze.catalog` software within your application's - environment: - - .. code-block:: text - - $ easy_install repoze.catalog - -#. Change your ZODB application's ``models.py`` file to look like the - below: - - .. code-block:: python - :linenos: - - from repoze.folder import Folder - from repoze.catalog.catalog import Catalog - from repoze.catalog.document import DocumentMap - from repoze.catalog.indexes.field import CatalogFieldIndex - - def get_title(object, default): - title = getattr(object, 'title', '') - if isinstance(title, basestring): - # lowercase for alphabetic sorting - title = title.lower() - return title - - class Document(Folder): - def __init__(self, title): - self.title = title - Folder.__init__(self) - - class Site(Folder): - def __init__(self): - self.catalog = Catalog() - self.catalog.document_map = DocumentMap() - self.update_indexes() - Folder.__init__(self) - - def update_indexes(self): - indexes = { - 'title': CatalogFieldIndex(get_title), - } - - catalog = self.catalog - - # add indexes - for name, index in indexes.iteritems(): - if name not in catalog: - catalog[name] = index - - # remove indexes - for name in catalog.keys(): - if name not in indexes: - del catalog[name] - - def appmaker(root): - if not 'site' in root: - root['site'] = Site() - transaction.commit() - return root['site'] - -#. We'll demonstrate how you might interact with a catalog from code - by manipulating the database directly using the ``pshell`` - command in a terminal window: - - .. code-block:: text - - [chrism@snowpro sess]$ ../bin/paster --plugin=pyramid pshell \ - development.ini myapp - Python 2.5.4 (r254:67916, Sep 4 2009, 02:12:16) - [GCC 4.2.1 (Apple Inc. build 5646)] on darwin - Type "help" for more information. "root" is the Pyramid app root object. - >>> from pyramid.traversal import resource_path - >>> from myapp.models import Document - >>> root['name'] = Document('title') - >>> doc = root['name'] - >>> docid = root.catalog.document_map.add(resource_path(doc)) - >>> root.catalog.index_doc(docid, doc) - >>> import transaction - >>> transaction.commit() - >>> root.catalog.search(title='title') - (1, IFSet([-787959756])) - -As you need them, add other indexes required by your application to -the catalog by modifying the ``update_indexes`` method of the ``Site`` -object. Whenever an index is added or removed, invoke the -``update_indexes`` method of the site (the root object) from a script -or from within a ``pshell`` session to update the set of indexes -used by your application. - -In :term:`view` code, you should be able to get a hold of he root -object via the :func:`pyramid.traversal.find_root` API. The -``catalog`` attribute of that root object will represent the catalog -previously added. - -Read the :mod:`repoze.catalog` `documentation -<http://docs.repoze.org/catalog>`_ for further information about other -types of indexes to add, using the document map, and how to issue -queries using the catalog query API. - -.. note:: - - The :mod:`repoze.folder` implementation sends events that can be - intercepted by a :term:`subscriber` when objects are added and - removed from a folder. It is often useful to hook these events for - the purpose of mutating the catalog when a new documentlike object - is added or removed. See the `repoze.folder documentation - <http://docs.repoze.org/folder>`_ for more information about the - events it sends. diff --git a/docs/tutorials/cmf/actions.rst b/docs/tutorials/cmf/actions.rst deleted file mode 100644 index a6e33fa59..000000000 --- a/docs/tutorials/cmf/actions.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. _actions_chapter: - -======= -Actions -======= - -In CMF, the "actions tool" along with "action providers" create an extensible -mechanism to show links in the CMF management UI that invoke a particular -behavior or which show a particular template. - -:app:`Pyramid` itself has no such concept, and no package provides a direct -replacement. Actions are such a generic concept that it's simple to -reimplement action-like navigation in a different way within any given -application. For example, a module-scope global dictionary which has keys -that are action names, and values which are tuples of (permission, link). -Take that concept and expand on it, and you'll have some passable actions -tool replacement within a single application. - -The `pyramid_viewgroup <https://github.com/Pylons/pyramid_viewgroup/>`_ -package provides some functionality for creating "view groups". Each view in -a viewgroup can provide some snippet of HTML (e.g. a single "tab"), and -individual views (tabs) within the group which cannot be displayed to the -user due to the user's lack of permissions will be omitted from the rendered -output. - -The :term:`repoze.lemonade` package provides "list item" support that -may be used to construct action lists. - diff --git a/docs/tutorials/cmf/catalog.rst b/docs/tutorials/cmf/catalog.rst deleted file mode 100644 index d5e9534ae..000000000 --- a/docs/tutorials/cmf/catalog.rst +++ /dev/null @@ -1,73 +0,0 @@ -.. _catalog_chapter: - -======= -Catalog -======= - -The main feature of the CMF catalog is that it filters search results -from the Zope 2 "catalog" based on the requesting user's ability to -view a particular cataloged object. - -:app:`Pyramid` itself has no cataloging facility, but an addon -package named :term:`repoze.catalog` offers similar functionality. - -Creating an Allowed Index -------------------------- - -In CMF, a catalog index named ``getAllowedRolesAndUsers`` along with -application indexing code allows for filtered search results. It's -reasonably easy to reproduce this pattern using some custom code. - -Creating The ``allowed`` Index -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here's some code which creates an ``allowed`` index for use in a -``repoze.catalog`` catalog:: - - from pyramid.security import principals_allowed_by_permission - from repoze.catalog.indexes.keyword import CatalogKeywordIndex - from repoze.catalog.catalog import Catalog - - class Allowed: - def __init__(self, permission): - self.permission = permission - - def __call__(self, context, default): - principals = principals_allowed_by_permission(context, - self.permission) - return principals - - def make_allowed_index(permission='View'): - index = CatalogKeywordIndex(Allowed(permission)) - return index - - index = make_allowed_index() - catalog = Catalog() - catalog['allowed'] = index - -When you index an item, the allowed index will be populated with all -the principal ids which have the 'View' permission. - -Using the ``allowed`` Index -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Here's how you might use the ``allowed`` index within a query:: - - from pyramid.security import effective_principals - principals = effective_principals(request) - catalog.searchResults(allowed={'operator':'or', 'query':principals}) - -The above query will return all document ids that the current user has -the 'View' permission against. Add other indexes to the query to get -a useful result. - -See the `repoze.catalog package -<http://svn.repoze.org/repoze.catalog/trunk>`_ for more information. - - - - - - - - diff --git a/docs/tutorials/cmf/content.rst b/docs/tutorials/cmf/content.rst deleted file mode 100644 index 85e5b5fbc..000000000 --- a/docs/tutorials/cmf/content.rst +++ /dev/null @@ -1,67 +0,0 @@ -.. _content_types_chapter: - -============= -Content Types -============= - -In CMF, a content type is defined as a bag of settings (the type -information, controlled within the "types tool"), as well as factory -code which generates an instance of that content. It is possible to -construct and enumerate content types using APIs defined on the types -tool. - -:app:`Pyramid` itself has no such concept, but an addon package named -:term:`repoze.lemonade` has a barebones replacement. - -Factory Type Information ------------------------- - -A factory type information object in CMF allows you to associate a -title, a description, an internationalization domain, an icon, an -initial view name, a factory, and a number of security settings with a -type name. Each type information object knows how to manufacture -content objects that match its type. - -:app:`Pyramid` certainly enforces none of these concepts in any -particular way, but :term:`repoze.lemonade` does. - -``repoze.lemonade`` Content -+++++++++++++++++++++++++++ - -:term:`repoze.lemonade` provides a reasonably handy directive and set -of helper functions which allow you to: - -#. Associate a interface with a factory function, making it into a - "content type". - -#. Enumerate all interfaces associated with factory functions. - -.. note:: Using this pattern is often plain silly, as it's usually - just as easy to actually import a class implementation and - create an instance directly using its constructor. But it - can be useful in cases where you want to address some set of - constructors uniformly without doing direct imports in the - code which performs the construction, or if you need to make - content construction uniform across a diverse set of model - types, or if you need to enumerate some set of information - about "content" types. It's left as an exercise to the - reader to determine under which circumstances using this - pattern is an appropriate thing to do. Hint: not very - often, unless you're doing the indirection solely to aid - content-agnostic unit tests or if you need to get an - enumerated subset of content type information to aid in UI - generation. That said, this *is* a tutorial about how to - get CMF-like features in :app:`Pyramid`, so we'll assume - the pattern is useful to readers. - -See the `repoze.lemonade package -<http://svn.repoze.org/repoze.lemonade/trunk>`_ for more information, -particularly its documentation for "content". - - - - - - - - diff --git a/docs/tutorials/cmf/index.rst b/docs/tutorials/cmf/index.rst deleted file mode 100644 index 26aa336a9..000000000 --- a/docs/tutorials/cmf/index.rst +++ /dev/null @@ -1,38 +0,0 @@ -Converting an Existing Zope/CMF Application to :app:`Pyramid` -================================================================ - -The Zope `Content Management Framework -<http://www.zope.org/Products/CMF/>`_ (aka CMF) is a layer on top of -:term:`Zope` 2 that provides facilities for creating content-driven -websites. It's reasonably easy to convert a modern Zope/CMF -application to :app:`Pyramid`. - -The main difference between CMF and :app:`Pyramid` is that :app:`Pyramid` -does not advertise itself as a system into which you can plug arbitrary -"packages" that extend a system-supplied management user interface. You -*could* build a CMF-like layer on top of :app:`Pyramid` but none currently -exists. For those sorts of high-extensibility, highly-regularized-UI -systems, CMF is still the better choice. - -:app:`Pyramid` (and other more lightweight systems) is often a -better choice when you're building the a user interface from scratch, -which often happens when the paradigms of some CMF-provided user -interface don't match the requirements of an application very closely. -Even so, a good number of developers tend to use CMF even when they do -start an application for which they need to build a UI from scratch, -because CMF happens to provide other helpful services, such as types, -skins, and workflow; this tutorial is for those sorts of developers -and projects. - -.. toctree:: - :maxdepth: 2 - - content.rst - catalog.rst - skins.rst - actions.rst - workflow.rst - missing.rst - - - diff --git a/docs/tutorials/cmf/missing.rst b/docs/tutorials/cmf/missing.rst deleted file mode 100644 index 964e0ab04..000000000 --- a/docs/tutorials/cmf/missing.rst +++ /dev/null @@ -1,22 +0,0 @@ -Missing Comparisons -=================== - -We currently don't have any comparative Pyramid-vs-CMF information -about the following concepts within this tutorial: - -- Templates - -- Forms - -- Membership - -- Discussions - -- Syndication - -- Dublincore - -Please ask on the `Pylons-devel maillist -<http://groups.google.com/group/pylons-devel>`_ or on the `#pylons IRC -channel <http://irc.freenode.net#pylons>`_ about these topics. - diff --git a/docs/tutorials/cmf/skins.rst b/docs/tutorials/cmf/skins.rst deleted file mode 100644 index 676a076b3..000000000 --- a/docs/tutorials/cmf/skins.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. _skins_chapter: - -===== -Skins -===== - -In CMF, a "skin layer" is defined as a collection of templates and -code (Python scripts, DTML methods, etc) that can be activated and -deactivated within a particular setup. A collection of active "skin -layers" grouped in a particular order forms a "skin". "Add-on" CMF -products often provide skin layers that are activated within a -particular skin to provide the site with additional features. - -To override static resources using a "search path" much like a set of -skin layers, :app:`Pyramid` provides the concept of -:term:`resource` overrides. See :ref:`overriding_resources_section` -for more information about resource overrides. - -While there is no analogue to a skin layer search path for locating -Python code (as opposed to resources), :term:`view` code combined with -differing :term:`predicate` arguments can provide a good deal of -the same sort of behavior. - diff --git a/docs/tutorials/cmf/workflow.rst b/docs/tutorials/cmf/workflow.rst deleted file mode 100644 index cc70d771a..000000000 --- a/docs/tutorials/cmf/workflow.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. _workflow_chapter: - -======== -Workflow -======== - -In CMF, the "workflow tool" allows developers to design state machines -which imply transition between content states. - -:app:`Pyramid` itself has no such concept, but the -:term:`repoze.workflow` package provides a simple state machine -implementation that can act as a barebones workflow tool. See its -documentation for more information. - diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index 766f05b56..5da7f32c7 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -14,12 +14,12 @@ This particular tutorial was developed under Apple's Mac OS X platform largely the same for all systems, delta specific path information for commands and files. -.. note:: Unfortunately these instructions almost certainly won't work - for deploying a :app:`Pyramid` application on a Windows system - using ``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 `mailing list - <http://lists.repoze.org/listinfo/repoze-dev>`_. +.. note:: Unfortunately these instructions almost certainly won't work for + deploying a :app:`Pyramid` application on a Windows system using + ``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>`_. #. The tutorial assumes you have Apache already installed on your system. If you do not, install Apache 2.X for your platform in @@ -75,13 +75,15 @@ commands and files. from pyramid.paster import get_app application = get_app( - '/Users/chrism/modwsgi/env/myapp/myapp.ini', 'main') + '/Users/chrism/modwsgi/env/myapp/production.ini', 'main') - The first argument to ``get_app`` is the project Paste - configuration file name. The second is the name of the section - within the .ini file that should be loaded by ``mod_wsgi``. The - assignment to the name ``application`` is important: mod_wsgi - requires finding such an assignment when it opens the file. + The first argument to ``get_app`` is the project Paste configuration file + name. It's best to use the ``production.ini`` file provided by your + Pyramid paster template, as it contains settings appropriate for + production. The second is the name of the section within the .ini file + that should be loaded by ``mod_wsgi``. The assignment to the name + ``application`` is important: mod_wsgi requires finding such an + assignment when it opens the file. #. Make the ``pyramid.wsgi`` script executable. @@ -100,7 +102,8 @@ commands and files. # play badly with C extensions. WSGIApplicationGroup %{GLOBAL} WSGIPassAuthorization On - WSGIDaemonProcess pyramid user=chrism group=staff processes=1 threads=4 \ + WSGIDaemonProcess pyramid user=chrism group=staff processes=1 \ + threads=4 \ python-path=/Users/chrism/modwsgi/env/lib/python2.6/site-packages WSGIScriptAlias /myapp /Users/chrism/modwsgi/env/pyramid.wsgi diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 34cde288f..ee86eb543 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -23,15 +23,13 @@ For any :app:`Pyramid` application to perform authorization, we need to add a registry` to add an :term:`authentication policy` and a :term:`authorization policy`. -Changing ``__init__.py`` -~~~~~~~~~~~~~~~~~~~~~~~~ +Adding Authentication and Authorization Policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We'll change our ``__init__.py`` file to enable an +We'll change our package's ``__init__.py`` file to enable an ``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable -declarative security checking. We'll also add a new view stanza, which -specifies a :term:`forbidden view`. This configures our login view to show -up when :app:`Pyramid` detects that a view invocation can not be authorized. -When you're done, your ``__init__.py`` will look like so: +declarative security checking. When you're done, your ``__init__.py`` will +look like so: .. literalinclude:: src/authorization/tutorial/__init__.py :linenos: @@ -84,6 +82,26 @@ and logout views. Add a file named ``login.py`` to your application :linenos: :language: python +Note that the ``login`` view callable in the ``login.py`` file has *two* view +configuration decorators. The order of these decorators is unimportant. +Each just adds a different :term:`view configuration` for the ``login`` view +callable. + +The first view configuration decorator configures the ``login`` view callable +so it will be invoked when someone visits ``/login`` (when the context is a +Wiki and the view name is ``login``). The second decorator (with context of +``pyramid.exceptions.Forbidden``) specifies a :term:`forbidden view`. This +configures our login view to be presented to the user when :app:`Pyramid` +detects that a view invocation can not be authorized. Because we've +configured a forbidden view, the ``login`` view callable will be invoked +whenever one of our users tries to execute a view callable that they are not +allowed to invoke as determined by the :term:`authorization policy` in use. +In our application, for example, this means that if a user has not logged in, +and he tries to add or edit a Wiki page, he will be shown the login form. +Before being allowed to continue on to the add or edit form, he will have to +provide credentials that give him permission to add or edit via this login +form. + Changing Existing Views ~~~~~~~~~~~~~~~~~~~~~~~ @@ -118,7 +136,6 @@ Add a ``login.pt`` template to your templates directory. It's referred to within the login view we just added to ``login.py``. .. literalinclude:: src/authorization/tutorial/templates/login.pt - :linenos: :language: xml Change ``view.pt`` and ``edit.pt`` @@ -128,27 +145,24 @@ We'll also need to change our ``edit.pt`` and ``view.pt`` templates to display a "Logout" link if someone is logged in. This link will invoke the logout view. -To do so we'll add this to both templates within the ``<div -class="main_content">`` div: +To do so we'll add this to both templates within the ``<div id="right" +class="app-welcome align-right">`` div: .. code-block:: xml - :linenos: <span tal:condition="logged_in"> <a href="${request.application_url}/logout">Logout</a> </span> -Giving Our Root Model Object an ACL ------------------------------------ +Giving Our Root Resource an ACL +------------------------------- -We need to give our root model object an :term:`ACL`. This ACL will -be sufficient to provide enough information to the :app:`Pyramid` -security machinery to challenge a user who doesn't have appropriate -credentials when he attempts to invoke the ``add_page`` or -``edit_page`` views. +We need to give our root resource object an :term:`ACL`. This ACL will be +sufficient to provide enough information to the :app:`Pyramid` security +machinery to challenge a user who doesn't have appropriate credentials when +he attempts to invoke the ``add_page`` or ``edit_page`` views. -We need to perform some imports at module scope in our ``models.py`` -file: +We need to perform some imports at module scope in our ``models.py`` file: .. code-block:: python :linenos: @@ -156,8 +170,8 @@ file: from pyramid.security import Allow from pyramid.security import Everyone -Our root model is a ``Wiki`` object. We'll add the following line at -class scope to our ``Wiki`` class: +Our root resource object is a ``Wiki`` instance. We'll add the following +line at class scope to our ``Wiki`` class: .. code-block:: python :linenos: @@ -165,12 +179,11 @@ class scope to our ``Wiki`` class: __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] -It's only happenstance that we're assigning this ACL at class scope. -An ACL can be attached to an object *instance* too; this is how "row -level security" can be achieved in :app:`Pyramid` applications. We -actually only need *one* ACL for the entire system, however, because -our security requirements are simple, so this feature is not -demonstrated. +It's only happenstance that we're assigning this ACL at class scope. An ACL +can be attached to an object *instance* too; this is how "row level security" +can be achieved in :app:`Pyramid` applications. We actually only need *one* +ACL for the entire system, however, because our security requirements are +simple, so this feature is not demonstrated. Our resulting ``models.py`` file will now look like so: @@ -181,76 +194,71 @@ Our resulting ``models.py`` file will now look like so: Adding ``permission`` Declarations to our ``view_config`` Decorators -------------------------------------------------------------------- -To protect each of our views with a particular permission, we need to -pass a ``permission`` argument to each of our -:class:`pyramid.view.view_config` decorators. To do so, within -``views.py``: +To protect each of our views with a particular permission, we need to pass a +``permission`` argument to each of our :class:`pyramid.view.view_config` +decorators. To do so, within ``views.py``: -- We add ``permission='view'`` to the decorator attached to the - ``view_wiki`` view function. This makes the assertion that only - users who possess the effective ``view`` permission at the time of - the request may invoke this view. We've granted - :data:`pyramid.security.Everyone` the view permission at the root - model via its ACL, so everyone will be able to invoke the - ``view_wiki`` view. +- We add ``permission='view'`` to the decorator attached to the ``view_wiki`` + view function. This makes the assertion that only users who possess the + ``view`` permission against the context resource at the time of the request + may invoke this view. We've granted :data:`pyramid.security.Everyone` the + view permission at the root model via its ACL, so everyone will be able to + invoke the ``view_wiki`` view. -- We add ``permission='view'`` to the decorator attached to the - ``view_page`` view function. This makes the assertion that only - users who possess the effective ``view`` permission at the time of +- We add ``permission='view'`` to the decorator attached to the ``view_page`` + view function. This makes the assertion that only users who possess the + effective ``view`` permission against the context resource at the time of the request may invoke this view. We've granted - :data:`pyramid.security.Everyone` the view permission at the root - model via its ACL, so everyone will be able to invoke the - ``view_page`` view. - -- We add ``permission='edit'`` to the decorator attached to the - ``add_page`` view function. This makes the assertion that only - users who possess the effective ``edit`` permission at the time of - the request may invoke this view. We've granted the - ``group:editors`` principal the ``edit`` permission at the root - model via its ACL, so only the a user whom is a member of the group - named ``group:editors`` will able to invoke the ``add_page`` view. - We've likewise given the ``editor`` user membership to this group - via thes ``security.py`` file by mapping him to the - ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function - consults the ``GROUPS`` data structure. This means that the - ``editor`` user can add pages. - -- We add ``permission='edit'`` to the decorator attached to the - ``edit_page`` view function. This makes the assertion that only - users who possess the effective ``edit`` permission at the time of - the request may invoke this view. We've granted the - ``group:editors`` principal the ``edit`` permission at the root - model via its ACL, so only the a user whom is a member of the group - named ``group:editors`` will able to invoke the ``edit_page`` view. - We've likewise given the ``editor`` user membership to this group - via thes ``security.py`` file by mapping him to the - ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function - consults the ``GROUPS`` data structure. This means that the - ``editor`` user can edit pages. + :data:`pyramid.security.Everyone` the view permission at the root model via + its ACL, so everyone will be able to invoke the ``view_page`` view. + +- We add ``permission='edit'`` to the decorator attached to the ``add_page`` + view function. This makes the assertion that only users who possess the + effective ``edit`` permission against the context resource at the time of + the request may invoke this view. We've granted the ``group:editors`` + principal the ``edit`` permission at the root model via its ACL, so only + the a user whom is a member of the group named ``group:editors`` will able + to invoke the ``add_page`` view. We've likewise given the ``editor`` user + membership to this group via thes ``security.py`` file by mapping him to + the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = + {'editor':['group:editors']}``); the ``groupfinder`` function consults the + ``GROUPS`` data structure. This means that the ``editor`` user can add + pages. + +- We add ``permission='edit'`` to the decorator attached to the ``edit_page`` + view function. This makes the assertion that only users who possess the + effective ``edit`` permission against the context resource at the time of + the request may invoke this view. We've granted the ``group:editors`` + principal the ``edit`` permission at the root model via its ACL, so only + the a user whom is a member of the group named ``group:editors`` will able + to invoke the ``edit_page`` view. We've likewise given the ``editor`` user + membership to this group via thes ``security.py`` file by mapping him to + the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = + {'editor':['group:editors']}``); the ``groupfinder`` function consults the + ``GROUPS`` data structure. This means that the ``editor`` user can edit + pages. Viewing the Application in a Browser ------------------------------------ -We can finally examine our application in a browser. The views we'll -try are as follows: +We can finally examine our application in a browser. The views we'll try are +as follows: -- Visiting ``http://localhost:6543/`` in a browser invokes the - ``view_wiki`` view. This always redirects to the ``view_page`` view - of the FrontPage page object. It is executable by any user. +- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` + view. This always redirects to the ``view_page`` view of the ``FrontPage`` + page resource. It is executable by any user. -- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes - the ``view_page`` view of the front page page object. This is - because it's the :term:`default view` (a view without a ``name``) - for ``Page`` objects. It is executable by any user. +- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes the + ``view_page`` view of the ``FrontPage`` Page resource. This is because + it's the :term:`default view` (a view without a ``name``) for ``Page`` + resources. It is executable by any user. -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page 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 - show the edit page form being displayed. +- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser invokes + the edit view for the ``FrontPage`` Page resource. It is executable by + only the ``editor`` user. If a different user (or the anonymous user) + invokes it, a login form will be displayed. Supplying the credentials with + the username ``editor``, password ``editor`` will show the edit page form + being displayed. - Visiting ``http://localhost:6543/add_page/SomePageName`` in a browser invokes the add view for a page. It is executable by only diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 8e6e89e57..c0faf30de 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -2,10 +2,9 @@ Basic Layout ============ -The starter files generated by the ``pyramid_zodb`` template are basic, -but they provide a good orientation for the high-level patterns common -to most :term:`traversal` -based :app:`Pyramid` (and :term:`ZODB` -based) projects. +The starter files generated by the ``pyramid_zodb`` template are basic, but +they provide a good orientation for the high-level patterns common to most +:term:`traversal` -based :app:`Pyramid` (and :term:`ZODB` based) projects. The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/ @@ -21,7 +20,7 @@ well as to contain application configuration code. When you run the application using the ``paster`` command using the ``development.ini`` generated config file, the application configuration -points at an Setuptools *entry point* described as ``egg:tutorial``. In our +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``: @@ -78,11 +77,10 @@ Resources and Models with ``models.py`` hierarchically in a :term:`resource tree`. This tree is consulted by :term:`traversal` to map URLs to code. In this application, the resource tree represents the site structure, but it *also* represents the -:term:`domain model` of the application, because eeach resource is a node +:term:`domain model` of the application, because each resource is a node stored persistently in a :term:`ZODB` database. The ``models.py`` file is where the ``pyramid_zodb`` Paster template put the classes that implement our -resource objects, each of which happens also to be a domain model -object. +resource objects, each of which happens also to be a domain model object. Here is the source for ``models.py``: @@ -171,7 +169,6 @@ The ``development.ini`` (in the tutorial :term:`project` directory, as opposed to the tutorial :term:`package` directory) looks like this: .. literalinclude:: src/views/development.ini - :linenos: :language: ini @@ -184,15 +181,22 @@ composed of :term:`middleware`. The ``egg:WebError#evalerror`` middleware is at the "top" of the pipeline. This is middleware which displays debuggable errors in the browser while -you're developing (not recommended for deployment). +you're developing (not recommended for a production system). The ``egg:repoze.zodbconn#closer`` middleware is in the middle of the pipeline. This is a piece of middleware which closes the ZODB connection opened by the ``PersistentApplicationFinder`` at the end of the request. -The ``egg:repoze.tm#tm`` middleware is the last piece of middleware in the -pipeline. This commits a transaction near the end of the request unless -there's an exception raised. +The ``egg:repoze.retry#retry`` middleware catches ``ConflictError`` +exceptions from ZODB and retries the request up to three times (ZODB is an +optimistic concurrency database that relies on application-level transaction +retries when a conflict occurs). + +The ``tm`` middleware is the last piece of middleware in the pipeline. This +commits a transaction near the end of the request unless there's an exception +raised or the HTTP response code is an error code. The ``tm`` refers to the +``[filter:tm]`` section beneath the pipeline declaration, which configures +the transaction manager. The final line in the ``[pipeline:main]`` section is ``tutorial``, which refers to the ``[app:tutorial]`` section above it. The ``[app:tutorial]`` diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index 078a8e014..5e4b8fb22 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -38,12 +38,11 @@ we're not going to use it. .. note:: - There is nothing automagically special about the filename - ``models.py``. A project may have many models throughout its - codebase in arbitrarily-named files. Files implementing models - often have ``model`` in their filenames (or they may live in a - Python subpackage of your application package named ``models``) , - but this is only by convention. + There is nothing automagically special about the filename ``models.py``. A + project may have many models throughout its codebase in arbitrarily-named + files. Files implementing models often have ``model`` in their filenames, + or they may live in a Python subpackage of your application package named + ``models``, but this is only by convention. Then, we'll add a ``Wiki`` class. Because this is a ZODB application, this class should inherit from :class:`persistent.mapping.PersistentMapping`. We @@ -131,6 +130,22 @@ When we're done changing ``tests.py``, it will look something like so: :linenos: :language: python +Declaring Dependencies in Our ``setup.py`` File +----------------------------------------------- + +Our application now depends on packages which are not dependencies of the +original "tutorial" application as it was generated by the ``paster create`` +command. We'll add these dependencies to our ``tutorial`` package's +``setup.py`` file by assigning these dependencies to both the +``install_requires`` and the ``tests_require`` parameters to the ``setup`` +function. In particular, we require the ``docutils`` package. + +Our resulting ``setup.py`` should look like so: + +.. literalinclude:: src/models/setup.py + :linenos: + :language: python + Running the Tests ----------------- @@ -148,7 +163,7 @@ On Windows: .. code-block:: text - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q + c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q The expected output is something like this: @@ -160,20 +175,3 @@ The expected output is something like this: OK -Declaring Dependencies in Our ``setup.py`` File ------------------------------------------------ - -Our application depends on packages which are not dependencies of the -original "tutorial" application as it was generated by the ``paster -create`` command. We'll add these dependencies to our ``tutorial`` -package's ``setup.py`` file by assigning these dependencies to both -the ``install_requires`` and the ``tests_require`` parameters to the -``setup`` function. In particular, we require the ``docutils`` -package. - -Our resulting ``setup.py`` should look like so: - -.. literalinclude:: src/models/setup.py - :linenos: - :language: python - diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 53f5ff994..31900233c 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -7,9 +7,10 @@ Conventionally, :term:`view callable` objects are defined within a automagically special about the filename ``views.py``. Files implementing views 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. However, a project may have many views throughout its codebase -in arbitrarily-named files. In this application, we'll be continuing to use -the ``views.py`` module, because there's no reason to break convention. +convention. A project may have many views throughout its codebase in +arbitrarily-named files. In this application, however, we'll be continuing +to use the ``views.py`` module, because there's no reason to break +convention. A :term:`view callable` in a :app:`Pyramid` application is typically a simple Python function that accepts a single parameter: :term:`request`. A view @@ -17,19 +18,22 @@ callable is assumed to return a :term:`response` object. However, a :app:`Pyramid` view can also be defined as callable which accepts *two* arguments: a :term:`context` and a :term:`request`. In :term:`url -dispatch` based applications, the context object is rarely used in the view +dispatch` based applications, the context resource is rarely used in the view body itself, so within code that uses URL-dispatch-only, it's common to -define views as callables that accept only a request to avoid the visual -"noise". This application, however, uses :term:`traversal` to map URLs to -resources, so we're often interested in the context; it's not "noise" to use. +define views as callables that accept only a ``request`` to avoid the visual +"noise" of a ``context`` argument. This application, however, uses +:term:`traversal` to map URLs to a context :term:`resource`, and since our +:term:`resource tree` also represents our application's "domain model", we're +often interested in the context, because it represents the persistent storage +of our application. For this reason, having ``context`` in the callable +argument list is not "noise" to us; instead it's actually rather important +within the view code we'll define in this application. The single-arg (``request`` -only) or two-arg (``context`` and ``request``) calling conventions will work in any :app:`Pyramid` application for any view; -they can be used interchangeably as necessary. In :term:`traversal` based -applications, such as the application we're building in this tutorial, the -context is used frequently within the body of a view method, so we'll be -using the two-argument ``(context, request)`` syntax in this application for -views that we add. +they can be used interchangeably as necessary. We'll be using the +two-argument ``(context, request)`` view callable argument list syntax in +this application. We're going to define several :term:`view callable` functions then wire them into :app:`Pyramid` using some :term:`view configuration`. @@ -42,7 +46,7 @@ Adding View Functions ===================== We're going to add four :term:`view callable` functions to our ``views.py`` -module. One view (named ``view_wiki``) will display the wiki itself (it will +module. One view named ``view_wiki`` will display the wiki itself (it will answer on the root URL), another named ``view_page`` will display an individual page, another named ``add_page`` will allow a page to be added, and a final view named ``edit_page`` will allow a page to be edited. @@ -51,36 +55,35 @@ The ``view_wiki`` view function ------------------------------- The ``view_wiki`` function will be configured to respond as the default view -callable for a ``Wiki`` resource object. We'll provide it with a -``@view_config`` decorator which names the class ``tutorial.models.Wiki`` as -its context. This means that when a Wiki object is the context, and no -:term:`view name` exists in the request, this view will be used. The view -configuration associated with ``view_wiki`` does not use a ``renderer`` -because the view callable always returns a :term:`response` object rather -than a dictionary. No renderer is necessary when a view returns a response -object. - -The view callable always redirects to the ``Page`` object named "FrontPage". -It returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class -(instances of which implement the WebOb :term:`response` interface), and the -:func:`pyramid.url.resource_url` API. :func:`pyramid.url.resource_url` -constructs a URL to the ``FrontPage`` page resource -(e.g. ``http://localhost:6543/FrontPage``), and uses it as the "location" of -the HTTPFound response, forming an HTTP redirect. +callable for a Wiki resource. We'll provide it with a ``@view_config`` +decorator which names the class ``tutorial.models.Wiki`` as its context. +This means that when a Wiki resource is the context, and no :term:`view name` +exists in the request, this view will be used. The view configuration +associated with ``view_wiki`` does not use a ``renderer`` because the view +callable always returns a :term:`response` object rather than a dictionary. +No renderer is necessary when a view returns a response object. + +The ``view_wiki`` view callable always redirects to the URL of a Page +resource named "FrontPage". To do so, it returns an instance of the +:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement +the WebOb :term:`response` interface). The :func:`pyramid.url.resource_url` +API. :func:`pyramid.url.resource_url` constructs a URL to the ``FrontPage`` +page resource (e.g. ``http://localhost:6543/FrontPage``), and uses it as the +"location" of the HTTPFound response, forming an HTTP redirect. The ``view_page`` view function ------------------------------- The ``view_page`` function will be configured to respond as the default view -of a ``Page`` resource. We'll provide it with a ``@view_config`` decorator -which names the class ``tutorial.models.Wiki`` as its context. This means -that when a Page object is the context, and no :term:`view name` exists in -the request, this view will be used. We inform :app:`Pyramid` this view will -use the ``templates/view.pt`` template file as a ``renderer``. +of a Page resource. We'll provide it with a ``@view_config`` decorator which +names the class ``tutorial.models.Page`` as its context. This means that +when a Page resource is the context, and no :term:`view name` exists in the +request, this view will be used. We inform :app:`Pyramid` this view will use +the ``templates/view.pt`` template file as a ``renderer``. The ``view_page`` function generates the :term:`ReStructuredText` body of a page (stored as the ``data`` attribute of the context passed to the view; the -context will be a Page object) as HTML. Then it substitutes an HTML anchor +context will be a Page resource) as HTML. Then it substitutes an HTML anchor for each *WikiWord* reference in the rendered HTML using a compiled regular expression. @@ -95,7 +98,7 @@ 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. +our current page resource. We then generate an edit URL (because it's easier to do here than in the template), and we wrap up a number of arguments in a dictionary and return @@ -117,13 +120,13 @@ callable. In the ``view_wiki`` view callable, we unconditionally return a The ``add_page`` view function ------------------------------ -The ``add_page`` function will be configured to respond when the context is a -Wiki and the :term:`view_name` is ``add_page``. We'll provide it with a -``@view_config`` decorator which names the string ``add_page`` as its -:term:`view name` (via name=), the class ``tutorial.models.Wiki`` as its +The ``add_page`` function will be configured to respond when the context +resource is a Wiki and the :term:`view name` is ``add_page``. We'll provide +it with a ``@view_config`` decorator which names the string ``add_page`` as +its :term:`view name` (via name=), the class ``tutorial.models.Wiki`` as its context, and the renderer named ``templates/edit.pt``. This means that when -a Wiki object is the context, and a :term:`view name` exists as the result of -traverasal named ``add_page``, this view will be used. We inform +a Wiki resource is the context, and a :term:`view name` named ``add_page`` +exists as the result of traversal, this view will be used. We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. We share the same template between add and edit views, thus ``edit.pt`` instead of ``add.pt``. @@ -131,9 +134,9 @@ a ``renderer``. We share the same template between add and edit views, thus The ``add_page`` function will be invoked when a user clicks on a WikiWord which isn't yet represented as a page in the system. The ``check`` function within the ``view_page`` view generates URLs to this view. It also acts as a -handler for the form that is generated when we want to add a page object. -The ``context`` of the ``add_page`` view is always a Wiki object (*not* a -Page object). +handler for the form that is generated when we want to add a page resource. +The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a +Page resource). The request :term:`subpath` in :app:`Pyramid` is the sequence of names that are found *after* the :term:`view name` in the URL segments given in the @@ -150,14 +153,14 @@ expression ``'form.submitted' in request.params`` is ``False``), the view renders a template. To do so, it generates a "save url" which the template use as the form post URL during rendering. We're lazy here, so we're trying 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 +the page edit view. To do so, we create a dummy Page resource object in +order to satisfy the edit form's desire to have *some* page object exposed as ``page``, and we'll render the template to a response. If the view rendering *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), we scrape the page body from the form data, create a Page object using the name in the subpath and -the page body, and save it into "our context" (the wiki) using the +the page body, and save it into "our context" (the Wiki) using the ``__setitem__`` method of the context. We then redirect back to the ``view_page`` view (the default view for a page) for the newly created page. @@ -165,23 +168,23 @@ The ``edit_page`` view function ------------------------------- The ``edit_page`` function will be configured to respond when the context is -a Page and the :term:`view_name` is ``edit_page``. We'll provide it with a -``@view_config`` decorator which names the string ``edit_page`` as its -:term:`view name` (via name=), the class ``tutorial.models.Page`` as its +a Page resource and the :term:`view name` is ``edit_page``. We'll provide it +with a ``@view_config`` decorator which names the string ``edit_page`` as its +:term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its context, and the renderer named ``templates/edit.pt``. This means that when -a Page object is the context, and a :term:`view name` exists as the result of -traverasal named ``edit_page``, this view will be used. We inform +a Page resource is the context, and a :term:`view name` exists as the result +of traverasal named ``edit_page``, this view will be used. We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. The ``edit_page`` function will be invoked when a user clicks the "Edit this Page" button on the view form. It renders an edit form but it also acts as -the handler for the form it renders. The ``context`` of the ``edit_page`` -view will *always* be a Page object (never a Wiki object). +the form post view callable for the form it renders. The ``context`` of the +``edit_page`` view will *always* be a Page resource (never a Wiki resource). If the view execution is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), the view -simply renders the edit form, passing the request, the page object, and a +simply renders the edit form, passing the request, the page resource, and a save_url which will be used as the action of the generated form. If the view execution *is* a result of a form submission (if the expression @@ -207,8 +210,8 @@ Most view callables we've added expected to be rendered via a :term:`template`. The default templating systems in :app:`Pyramid` are :term:`Chameleon` and :term:`Mako`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language. Mako is a non-XML-based -templating language. Because we have to pick one, we'll use Chameleon for -this tutorial. +templating language. Because we had to pick one, we chose Chameleon for this +tutorial. The templates we create will live in the ``templates`` directory of our tutorial package. Chameleon templates must have a ``.pt`` extension to be @@ -217,8 +220,8 @@ recognized as such. The ``view.pt`` Template ------------------------ -The ``view.pt`` template is used for viewing a single wiki page. It is used -by the ``view_page`` view function. It should have a div that is "structure +The ``view.pt`` template is used for viewing a single Page. It is used by +the ``view_page`` view function. It should have a div that is "structure replaced" with the ``content`` value provided by the view. It should also have a link on the rendered page that points at the "edit" URL (the URL which invokes the ``edit_page`` view for the page being viewed). @@ -227,7 +230,6 @@ Once we're done with the ``view.pt`` template, it will look a lot like the below: .. literalinclude:: src/views/tutorial/templates/view.pt - :linenos: :language: xml .. note:: The names available for our use in a template are always those that @@ -243,34 +245,35 @@ the below: The ``edit.pt`` Template ------------------------ -The ``edit.pt`` template is used for adding and editing a wiki page. It is -used by the ``add_page`` and ``edit_page`` view functions. It should display -a page containing a form that POSTs back to the "save_url" argument supplied -by the view. The form should have a "body" textarea field (the page data), -and a submit button that has the name "form.submitted". The textarea in the -form should be filled with any existing page data when it is rendered. +The ``edit.pt`` template is used for adding and editing a Page. It is used +by the ``add_page`` and ``edit_page`` view functions. It should display a +page containing a form that POSTs back to the "save_url" argument supplied by +the view. The form should have a "body" textarea field (the page data), and +a submit button that has the name "form.submitted". The textarea in the form +should be filled with any existing page data when it is rendered. Once we're done with the ``edit.pt`` template, it will look a lot like the below: .. literalinclude:: src/views/tutorial/templates/edit.pt - :linenos: :language: xml -Static Resources ----------------- +Static Assets +------------- -Our templates name a single static resource named ``style.css``. We need to -create this and place it in a file named ``style.css`` within our package's -``static`` directory. This file is a little too long to replicate within the -body of this guide, however it is available `online -<http://github.com/Pylons/pyramid/blob/master/docs/tutorials/wiki/src/views/tutorial/static/style.css>`_. +Our templates name a single static asset named ``pylons.css``. We don't need +to create this file within our package's ``static`` directory because it was +provided at the time we created the project. This file is a little too long to +replicate within the body of this guide, however it is available `online +<http://github.com/Pylons/pyramid/blob/master/docs/tutorials/wiki/src/views/tutorial/static/pylons.css>`_. This CSS file will be accessed via -e.g. ``http://localhost:6543/static/style.css`` by virtue of the call to +e.g. ``http://localhost:6543/static/pylons.css`` by virtue of the call to ``add_static_view`` directive we've made in the ``__init__`` file. Any -number and type of static resources can be placed in this directory (or -subdirectories) and are just referred to by URL within templates. +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. Testing the Views ================= @@ -305,7 +308,7 @@ On Windows: .. code-block:: text - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q + c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q The expected result looks something like: @@ -323,20 +326,20 @@ Viewing the Application in a Browser Once we've completed our edits, we can finally examine our application in a browser. The views we'll try are as follows: -- Visiting ``http://localhost:6543/`` in a browser invokes the - ``view_wiki`` view. This always redirects to the ``view_page`` view - of the FrontPage page object. +- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` + view. This always redirects to the ``view_page`` view of the ``FrontPage`` + Page resource. - Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes - the ``view_page`` view of the front page page object. This is + the ``view_page`` view of the front page resource. This is because it's the *default view* (a view without a ``name``) for Page - objects. + resources. - Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page object. + invokes the edit view for the ``FrontPage`` Page resource. - Visiting ``http://localhost:6543/add_page/SomePageName`` in a - browser invokes the add view for a page. + browser invokes the add view for a Page. - To generate an error, visit ``http://localhost:6543/add_page`` which will generate an ``IndexError`` for the expression diff --git a/docs/tutorials/wiki/distributing.rst b/docs/tutorials/wiki/distributing.rst index ad717e72a..ed0af222f 100644 --- a/docs/tutorials/wiki/distributing.rst +++ b/docs/tutorials/wiki/distributing.rst @@ -18,14 +18,7 @@ On Windows: .. code-block:: text - c:\bigfntut> ..\Scripts\python setup.py sdist - -.. warning:: If your project files are not checked in to a version - control repository (such as Subversion), the dist tarball will - *not* contain all the files it needs to. In particular, it will - not contain non-Python-source files (such as templates and static - files). To ensure that these are included, check your files into a - version control repository before running ``setup.py sdist``. + c:\pyramidtut> ..\Scripts\python setup.py sdist The output of such a command will be something like: diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst index 589935047..660bf3bd3 100644 --- a/docs/tutorials/wiki/index.rst +++ b/docs/tutorials/wiki/index.rst @@ -1,7 +1,7 @@ .. _bfg_wiki_tutorial: -ZODB + Traversal Wiki Tutorial (For Developers Familiar with Zope) -================================================================== +ZODB + Traversal Wiki Tutorial +============================== This tutorial introduces a :term:`traversal` -based :app:`Pyramid` application to a developer familiar with Python. It will be most familiar to diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index 82265170d..208be68f8 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -21,33 +21,39 @@ Preparation, UNIX your system, obtain, install, or find `Python 2.6 <http://python.org/download/releases/2.6.6/>`_ for your system. +#. Make sure the Python development headers are installed on your system. If + you've installed Python from source, these will already be installed. If + you're using a system Python, you may have to install a ``python-dev`` + package (e.g. ``apt-get python-dev``). The headers are not required for + Pyramid itself, just for dependencies of the tutorial. + #. Install the latest `setuptools` into the Python you obtained/installed/found in the step above: download `ez_setup.py <http://peak.telecommunity.com/dist/ez_setup.py>`_ and run it using the ``python`` interpreter of your Python 2.6 installation: - .. code-block:: bash + .. code-block:: text $ /path/to/my/Python-2.6/bin/python ez_setup.py #. Use that Python's `bin/easy_install` to install `virtualenv`: - .. code-block:: bash + .. code-block:: text $ /path/to/my/Python-2.6/bin/easy_install virtualenv #. Use that Python's virtualenv to make a workspace: - .. code-block:: bash + .. code-block:: text $ path/to/my/Python-2.6/bin/virtualenv --no-site-packages \ - bigfntut + pyramidtut -#. Switch to the ``bigfntut`` directory: +#. Switch to the ``pyramidtut`` directory: - .. code-block:: bash + .. code-block:: text - $ cd bigfntut + $ cd pyramidtut #. (Optional) Consider using ``source bin/activate`` to make your shell environment wired to use the virtualenv. @@ -55,16 +61,16 @@ Preparation, UNIX #. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies installed: - .. code-block:: bash + .. code-block:: text $ bin/easy_install pyramid -#. Use ``easy_install`` to install ``docutils``, ``repoze.tm``, +#. Use ``easy_install`` to install ``docutils``, ``repoze.tm2``, ``repoze.zodbconn``, ``nose`` and ``coverage``: - .. code-block:: bash + .. code-block:: text - $ bin/easy_install docutils repoze.tm repoze.zodbconn \ + $ bin/easy_install docutils repoze.tm2 repoze.zodbconn \ nose coverage Preparation, Windows @@ -79,27 +85,27 @@ Preparation, Windows the ``python`` interpreter of your Python 2.6 installation using a command prompt: - .. code-block:: bat + .. code-block:: text c:\> c:\Python26\python ez_setup.py #. Use that Python's `bin/easy_install` to install `virtualenv`: - .. code-block:: bat + .. code-block:: text c:\> c:\Python26\Scripts\easy_install virtualenv #. Use that Python's virtualenv to make a workspace: - .. code-block:: bat + .. code-block:: text - c:\> c:\Python26\Scripts\virtualenv --no-site-packages bigfntut + c:\> c:\Python26\Scripts\virtualenv --no-site-packages pyramidtut -#. Switch to the ``bigfntut`` directory: +#. Switch to the ``pyramidtut`` directory: - .. code-block:: bat + .. code-block:: text - c:\> cd bigfntut + c:\> cd pyramidtut #. (Optional) Consider using ``bin\activate.bat`` to make your shell environment wired to use the virtualenv. @@ -107,16 +113,16 @@ Preparation, Windows #. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies installed: - .. code-block:: bat + .. code-block:: text - c:\bigfntut> Scripts\easy_install pyramid + c:\pyramidtut> Scripts\easy_install pyramid -#. Use ``easy_install`` to install ``docutils``, ``repoze.tm``, +#. Use ``easy_install`` to install ``docutils``, ``repoze.tm2``, ``repoze.zodbconn``, ``nose`` and ``coverage``: - .. code-block:: bat + .. code-block:: text - c:\bigfntut> Scripts\easy_install docutils repoze.tm \ + c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 \ repoze.zodbconn nose coverage .. _making_a_project: @@ -129,19 +135,19 @@ variety of templates to generate sample projects. For this tutorial, we will use the :term:`ZODB` -oriented template named ``pyramid_zodb``. The below instructions assume your current working directory is the -"virtualenv" named "bigfntut". +"virtualenv" named "pyramidtut". On UNIX: -.. code-block:: bash +.. code-block:: text $ bin/paster create -t pyramid_zodb tutorial On Windows: -.. code-block:: bat +.. code-block:: text - c:\bigfntut> Scripts\paster create -t pyramid_zodb tutorial + c:\pyramidtut> Scripts\paster create -t pyramid_zodb tutorial .. note:: If you are using Windows, the ``pyramid_zodb`` Paster template doesn't currently deal gracefully with installation into a location @@ -160,17 +166,17 @@ directory you created in :ref:`making_a_project`, and run the On UNIX: -.. code-block:: bash +.. code-block:: text $ cd tutorial $ ../bin/python setup.py develop On Windows: -.. code-block:: bat +.. code-block:: text - C:\bigfntut> cd tutorial - C:\bigfntut\tutorial> ..\Scripts\python setup.py develop + C:\pyramidtut> cd tutorial + C:\pyramidtut\tutorial> ..\Scripts\python setup.py develop .. _running_tests: @@ -182,15 +188,15 @@ the tests for the project. On UNIX: -.. code-block:: bash +.. code-block:: text $ ../bin/python setup.py test -q On Windows: -.. code-block:: bat +.. code-block:: text - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q + c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q Starting the Application ======================== @@ -199,15 +205,15 @@ Start the application. On UNIX: -.. code-block:: bash +.. code-block:: text $ ../bin/paster serve development.ini --reload On Windows: -.. code-block:: bat +.. code-block:: text - c:\bifgfntut\tutorial> ..\Scripts\paster serve development.ini --reload + c:\pyramidtut\tutorial> ..\Scripts\paster serve development.ini --reload Exposing Test Coverage Information ================================== @@ -220,15 +226,15 @@ tests. On UNIX: -.. code-block:: bash +.. code-block:: text $ ../bin/nosetests --cover-package=tutorial --cover-erase --with-coverage On Windows: -.. code-block:: bat +.. code-block:: text - c:\bigfntut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \ + c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \ --cover-erase --with-coverage Looks like the code in the ``pyramid_zodb`` template for ZODB projects is diff --git a/docs/tutorials/wiki/src/authorization/MANIFEST.in b/docs/tutorials/wiki/src/authorization/MANIFEST.in new file mode 100644 index 000000000..81beba1b1 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/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/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index 6f4c33d93..1ba746d0e 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -12,9 +12,14 @@ zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 pipeline = egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.tm#tm + egg:repoze.retry#retry + tm tutorial +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki/src/authorization/production.ini b/docs/tutorials/wiki/src/authorization/production.ini new file mode 100644 index 000000000..5c47ade9b --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/production.ini @@ -0,0 +1,71 @@ +[app:tutorial] +use = egg:tutorial +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en +zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + +[pipeline:main] +pipeline = + weberror + egg:repoze.zodbconn#closer + egg:repoze.retry#retry + tm + tutorial + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[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][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/authorization/setup.py b/docs/tutorials/wiki/src/authorization/setup.py index c1b8e9f33..adfa70c9f 100644 --- a/docs/tutorials/wiki/src/authorization/setup.py +++ b/docs/tutorials/wiki/src/authorization/setup.py @@ -9,7 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm', + 'repoze.tm2>=1.0b1', # default_commit_veto + 'repoze.retry', 'ZODB3', 'WebError', 'docutils', diff --git a/docs/tutorials/wiki/src/authorization/tutorial/login.py b/docs/tutorials/wiki/src/authorization/tutorial/login.py index 59e71a1d9..463db71a6 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/login.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/login.py @@ -7,10 +7,10 @@ from pyramid.url import resource_url from tutorial.security import USERS -@view_config(context='pyramid.exceptions.Forbidden', - renderer='templates/login.pt') @view_config(context='tutorial.models.Wiki', name='login', renderer='templates/login.pt') +@view_config(context='pyramid.exceptions.Forbidden', + renderer='templates/login.pt') def login(request): login_url = resource_url(request.context, request, 'login') referrer = request.url diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/footerbg.png b/docs/tutorials/wiki/src/authorization/tutorial/static/footerbg.png Binary files differnew file mode 100644 index 000000000..1fbc873da --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/footerbg.png diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/headerbg.png b/docs/tutorials/wiki/src/authorization/tutorial/static/headerbg.png Binary files differnew file mode 100644 index 000000000..0596f2020 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/headerbg.png diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/ie6.css b/docs/tutorials/wiki/src/authorization/tutorial/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/logo.png b/docs/tutorials/wiki/src/authorization/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki/src/authorization/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/middlebg.png b/docs/tutorials/wiki/src/authorization/tutorial/static/middlebg.png Binary files differnew file mode 100644 index 000000000..2369cfb7d --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/middlebg.png diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css b/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css index c153be07f..fd1914d8d 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/pylons.css @@ -4,34 +4,23 @@ 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;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} @@ -42,23 +31,26 @@ 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;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#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;} @@ -67,7 +59,7 @@ 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=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-small.png b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-small.png diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png Binary files differnew file mode 100644 index 000000000..347e05549 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/style.css b/docs/tutorials/wiki/src/authorization/tutorial/static/style.css deleted file mode 100644 index cad87e0d4..000000000 --- a/docs/tutorials/wiki/src/authorization/tutorial/static/style.css +++ /dev/null @@ -1,109 +0,0 @@ -html, body { - color: black; - background-color: #ddd; - font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, sans-serif; - margin: 0; - padding: 0; -} - -td, th {padding:3px;border:none;} -tr th {text-align:left;background-color:#f0f0f0;color:#333;} -tr.odd td {background-color:#edf3fe;} -tr.even td {background-color:#fff;} - -#header { - height: 80px; - width: 777px; - background: blue URL('../images/header_inner.png') no-repeat; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - margin: 0 auto 0 auto; -} - -a.link, a, a.active { - color: #369; -} - - -#main_content { - color: black; - font-size: 127%; - background-color: white; - width: 757px; - margin: 0 auto 0 auto; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - padding: 10px; -} - -#sidebar { - border: 1px solid #aaa; - background-color: #eee; - margin: 0.5em; - padding: 1em; - float: right; - width: 200px; - font-size: 88%; -} - -#sidebar h2 { - margin-top: 0; -} - -#sidebar ul { - margin-left: 1.5em; - padding-left: 0; -} - -h1,h2,h3,h4,h5,h6,#getting_started_steps { - font-family: "Century Schoolbook L", Georgia, serif; - font-weight: bold; -} - -h2 { - font-size: 150%; -} - -#footer { - border: 1px solid #aaa; - border-top: 0px none; - color: #999; - background-color: white; - padding: 10px; - font-size: 80%; - text-align: center; - width: 757px; - margin: 0 auto 1em auto; -} - -.code { - font-family: monospace; -} - -span.code { - font-weight: bold; - background: #eee; -} - -#status_block { - margin: 0 auto 0.5em auto; - padding: 15px 10px 15px 55px; - background: #cec URL('../images/ok.png') left center no-repeat; - border: 1px solid #9c9; - width: 450px; - font-size: 120%; - font-weight: bolder; -} - -.notice { - margin: 0.5em auto 0.5em auto; - padding: 15px 10px 15px 55px; - width: 450px; - background: #eef URL('../images/info.png') left center no-repeat; - border: 1px solid #cce; -} - -.fielderror { - color: red; - font-weight: bold; -} diff --git a/docs/tutorials/wiki/src/authorization/tutorial/static/transparent.gif b/docs/tutorials/wiki/src/authorization/tutorial/static/transparent.gif Binary files differnew file mode 100644 index 000000000..0341802e5 --- /dev/null +++ b/docs/tutorials/wiki/src/authorization/tutorial/static/transparent.gif diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt index 5f8b22207..f9da6c414 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt @@ -1,34 +1,62 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki) - Editing: ${page.__name__}</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <title>${page.__name__} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/ie6.css')}" + type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> - <body> - -<div class="main_content"> - <div style="float:right; width: 10em;"> Viewing - <span tal:replace="page.__name__">Page Name Goes Here</span> <br/> - You can return to the <a href="${request.application_url}" - >FrontPage</a>. - <span tal:condition="logged_in"><a - href="${request.application_url}/logout">Logout</a></span> + <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"> + Editing <b><span tal:replace="page.__name__">Page Name + Goes Here</span></b><br/> + You can return to the + <a href="${request.application_url}">FrontPage</a>.<br/> + </div> + <div id="right" class="app-welcome align-right"> + <span tal:condition="logged_in"> + <a href="${request.application_url}/logout">Logout</a> + </span> + </div> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <form action="${save_url}" method="post"> + <textarea name="body" tal:content="page.data" rows="10" + cols="60"/><br/> + <input type="submit" name="form.submitted" value="Save"/> + </form> + </div> + </div> </div> - - <div> - <form action="${save_url}" method="post"> - <textarea name="body" tal:content="page.data" rows="10" cols="60"/> - <input type="submit" name="form.submitted" value="Save"/> - </form> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> </div> -</div> </body> </html> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt index c56983d64..64e592ea9 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt @@ -1,32 +1,58 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" - xmlns:tal="http://xml.zope.org/namespaces/tal"> - +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <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> - -<h1>Log In</h1> - -<div tal:replace="message"/> - -<div class="main_content"> - <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 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> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> </html> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt index a5a0dd214..d98420680 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt @@ -1,69 +1,75 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.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&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> <body> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <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/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 id="footer"> - <div class="footer">© Copyright 2008-2010, Agendaless Consulting.</div> - </div> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Go" /> + </form> + </div> + <div id="right" class="align-left"> + <h2>Pyramid links</h2> + <ul class="links"> + <li> + <a href="http://pylonsproject.org">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt index f957176f1..d207a0c23 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt @@ -1,31 +1,65 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>${page.__name__} - Pyramid tutorial wiki - (based on TurboGears 20-Minute Wiki) - </title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <title>${page.__name__} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/ie6.css')}" + type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> - <body> - -<div class="main_content"> -<div style="float:right; width: 10em;"> Viewing -<span tal:replace="page.__name__">Page Name Goes Here</span> <br/> -You can return to the <a href="${request.application_url}">FrontPage</a>. -<span tal:condition="logged_in"> - <a href="${request.application_url}/logout">Logout</a> -</span> -</div> - -<div tal:replace="structure content">Page text goes here.</div> -<p><a tal:attributes="href edit_url" href="">Edit this page</a></p> -</div> - -</body></html> + <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"> + Viewing <b><span tal:replace="page.__name__">Page Name + Goes Here</span></b><br/> + You can return to the + <a href="${request.application_url}">FrontPage</a>.<br/> + </div> + <div id="right" class="app-welcome align-right"> + <span tal:condition="logged_in"> + <a href="${request.application_url}/logout">Logout</a> + </span> + </div> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div tal:replace="structure content"> + Page text goes here. + </div> + <p> + <a tal:attributes="href edit_url" href=""> + Edit this page + </a> + </p> + </div> + </div> + </div> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> + </div> +</body> +</html> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py index 183cb2a8d..a83e17de4 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py @@ -13,7 +13,7 @@ wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") @view_config(context='tutorial.models.Wiki', permission='view') def view_wiki(context, request): - return HTTPFound(location = resource_url(context, request, 'FrontPage')) + return HTTPFound(location=resource_url(context, request, 'FrontPage')) @view_config(context='tutorial.models.Page', renderer='templates/view.pt', permission='view') diff --git a/docs/tutorials/wiki/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki/src/basiclayout/MANIFEST.in new file mode 100644 index 000000000..81beba1b1 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/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/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index 6f4c33d93..555010bed 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -12,9 +12,14 @@ zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 pipeline = egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.tm#tm + egg:repoze.retry#retry + tm tutorial +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + [server:main] use = egg:Paste#http host = 0.0.0.0 @@ -42,6 +47,6 @@ 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] %(message)s # End logging configuration diff --git a/docs/tutorials/wiki/src/basiclayout/production.ini b/docs/tutorials/wiki/src/basiclayout/production.ini new file mode 100644 index 000000000..5c47ade9b --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/production.ini @@ -0,0 +1,71 @@ +[app:tutorial] +use = egg:tutorial +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en +zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + +[pipeline:main] +pipeline = + weberror + egg:repoze.zodbconn#closer + egg:repoze.retry#retry + tm + tutorial + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[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][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index 7fb15b782..2d540d65b 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -9,7 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm', + 'repoze.tm2>=1.0b1', # default_commit_veto + 'repoze.retry', 'ZODB3', 'WebError', ] diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/footerbg.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/footerbg.png Binary files differnew file mode 100644 index 000000000..1fbc873da --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/footerbg.png diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/headerbg.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/headerbg.png Binary files differnew file mode 100644 index 000000000..0596f2020 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/headerbg.png diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/ie6.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/logo.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/middlebg.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/middlebg.png Binary files differnew file mode 100644 index 000000000..2369cfb7d --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/middlebg.png diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css index c153be07f..fd1914d8d 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pylons.css @@ -4,34 +4,23 @@ 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;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} @@ -42,23 +31,26 @@ 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;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#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;} @@ -67,7 +59,7 @@ 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=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-small.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-small.png diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png Binary files differnew file mode 100644 index 000000000..347e05549 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/static/transparent.gif b/docs/tutorials/wiki/src/basiclayout/tutorial/static/transparent.gif Binary files differnew file mode 100644 index 000000000..0341802e5 --- /dev/null +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/static/transparent.gif diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index 6ad23d44f..c24daa711 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -1,79 +1,75 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.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&subset=latin" type="text/css" media="screen" charset="utf-8" /> - <!--[if !IE 7]> - <style type="text/css"> - #wrap {display:table;height:100%} - </style> - <![endif]--> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> <body> - <div id="wrap"> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <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> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Go" /> + </form> + </div> + <div id="right" class="align-left"> + <h2>Pyramid links</h2> + <ul class="links"> + <li> + <a href="http://pylonsproject.org/">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py index 0a3d507a0..1f3c3bb4d 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py @@ -1,15 +1,13 @@ import unittest -from pyramid.config import Configurator from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() def test_my_view(self): from tutorial.views import my_view diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py index 555f49e6d..157b9ac8f 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py @@ -1,6 +1,7 @@ from pyramid.view import view_config from tutorial.models import MyModel -@view_config(context=MyModel, renderer='tutorial:templates/mytemplate.pt') +@view_config(context=MyModel, + renderer='tutorial:templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/models/MANIFEST.in b/docs/tutorials/wiki/src/models/MANIFEST.in new file mode 100644 index 000000000..81beba1b1 --- /dev/null +++ b/docs/tutorials/wiki/src/models/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/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index 6f4c33d93..1ba746d0e 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -12,9 +12,14 @@ zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 pipeline = egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.tm#tm + egg:repoze.retry#retry + tm tutorial +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki/src/models/production.ini b/docs/tutorials/wiki/src/models/production.ini new file mode 100644 index 000000000..5c47ade9b --- /dev/null +++ b/docs/tutorials/wiki/src/models/production.ini @@ -0,0 +1,71 @@ +[app:tutorial] +use = egg:tutorial +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en +zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + +[pipeline:main] +pipeline = + weberror + egg:repoze.zodbconn#closer + egg:repoze.retry#retry + tm + tutorial + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[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][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/models/setup.py b/docs/tutorials/wiki/src/models/setup.py index 5034a3989..daa5e5eb1 100644 --- a/docs/tutorials/wiki/src/models/setup.py +++ b/docs/tutorials/wiki/src/models/setup.py @@ -9,7 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm', + 'repoze.tm2>=1.0b1', # default_commit_veto + 'repoze.retry', 'ZODB3', 'WebError', 'docutils', diff --git a/docs/tutorials/wiki/src/models/tutorial/static/footerbg.png b/docs/tutorials/wiki/src/models/tutorial/static/footerbg.png Binary files differnew file mode 100644 index 000000000..1fbc873da --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/static/footerbg.png diff --git a/docs/tutorials/wiki/src/models/tutorial/static/headerbg.png b/docs/tutorials/wiki/src/models/tutorial/static/headerbg.png Binary files differnew file mode 100644 index 000000000..0596f2020 --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/static/headerbg.png diff --git a/docs/tutorials/wiki/src/models/tutorial/static/ie6.css b/docs/tutorials/wiki/src/models/tutorial/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/docs/tutorials/wiki/src/models/tutorial/static/logo.png b/docs/tutorials/wiki/src/models/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki/src/models/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki/src/models/tutorial/static/middlebg.png b/docs/tutorials/wiki/src/models/tutorial/static/middlebg.png Binary files differnew file mode 100644 index 000000000..2369cfb7d --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/static/middlebg.png diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pylons.css b/docs/tutorials/wiki/src/models/tutorial/static/pylons.css index c153be07f..a9f49cc85 100644 --- a/docs/tutorials/wiki/src/models/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/models/tutorial/static/pylons.css @@ -4,34 +4,23 @@ 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;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} @@ -42,23 +31,26 @@ 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;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-snall,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#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;} @@ -67,7 +59,7 @@ 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=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pyramid-small.png b/docs/tutorials/wiki/src/models/tutorial/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/static/pyramid-small.png diff --git a/docs/tutorials/wiki/src/models/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/models/tutorial/static/pyramid.png Binary files differnew file mode 100644 index 000000000..347e05549 --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/static/pyramid.png diff --git a/docs/tutorials/wiki/src/models/tutorial/static/transparent.gif b/docs/tutorials/wiki/src/models/tutorial/static/transparent.gif Binary files differnew file mode 100644 index 000000000..0341802e5 --- /dev/null +++ b/docs/tutorials/wiki/src/models/tutorial/static/transparent.gif diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index a5a0dd214..d98420680 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -1,69 +1,75 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.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&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> <body> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <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/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 id="footer"> - <div class="footer">© Copyright 2008-2010, Agendaless Consulting.</div> - </div> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Go" /> + </form> + </div> + <div id="right" class="align-left"> + <h2>Pyramid links</h2> + <ul class="links"> + <li> + <a href="http://pylonsproject.org">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py index 839964538..51c97a95d 100644 --- a/docs/tutorials/wiki/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki/src/models/tutorial/tests.py @@ -1,6 +1,5 @@ import unittest -from pyramid.config import Configurator from pyramid import testing class PageModelTests(unittest.TestCase): @@ -50,11 +49,10 @@ class AppmakerTests(unittest.TestCase): class ViewTests(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() def test_my_view(self): from tutorial.views import my_view diff --git a/docs/tutorials/wiki/src/views/MANIFEST.in b/docs/tutorials/wiki/src/views/MANIFEST.in new file mode 100644 index 000000000..81beba1b1 --- /dev/null +++ b/docs/tutorials/wiki/src/views/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/views/development.ini b/docs/tutorials/wiki/src/views/development.ini index 6f4c33d93..555010bed 100644 --- a/docs/tutorials/wiki/src/views/development.ini +++ b/docs/tutorials/wiki/src/views/development.ini @@ -12,9 +12,14 @@ zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 pipeline = egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:repoze.tm#tm + egg:repoze.retry#retry + tm tutorial +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + [server:main] use = egg:Paste#http host = 0.0.0.0 @@ -42,6 +47,6 @@ 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] %(message)s # End logging configuration diff --git a/docs/tutorials/wiki/src/views/production.ini b/docs/tutorials/wiki/src/views/production.ini new file mode 100644 index 000000000..5c47ade9b --- /dev/null +++ b/docs/tutorials/wiki/src/views/production.ini @@ -0,0 +1,71 @@ +[app:tutorial] +use = egg:tutorial +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en +zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + +[pipeline:main] +pipeline = + weberror + egg:repoze.zodbconn#closer + egg:repoze.retry#retry + tm + tutorial + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[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][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/views/setup.py b/docs/tutorials/wiki/src/views/setup.py index 5034a3989..daa5e5eb1 100644 --- a/docs/tutorials/wiki/src/views/setup.py +++ b/docs/tutorials/wiki/src/views/setup.py @@ -9,7 +9,8 @@ CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'repoze.zodbconn', - 'repoze.tm', + 'repoze.tm2>=1.0b1', # default_commit_veto + 'repoze.retry', 'ZODB3', 'WebError', 'docutils', diff --git a/docs/tutorials/wiki/src/views/tutorial/static/footerbg.png b/docs/tutorials/wiki/src/views/tutorial/static/footerbg.png Binary files differnew file mode 100644 index 000000000..1fbc873da --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/static/footerbg.png diff --git a/docs/tutorials/wiki/src/views/tutorial/static/headerbg.png b/docs/tutorials/wiki/src/views/tutorial/static/headerbg.png Binary files differnew file mode 100644 index 000000000..0596f2020 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/static/headerbg.png diff --git a/docs/tutorials/wiki/src/views/tutorial/static/ie6.css b/docs/tutorials/wiki/src/views/tutorial/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/docs/tutorials/wiki/src/views/tutorial/static/logo.png b/docs/tutorials/wiki/src/views/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki/src/views/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki/src/views/tutorial/static/middlebg.png b/docs/tutorials/wiki/src/views/tutorial/static/middlebg.png Binary files differnew file mode 100644 index 000000000..2369cfb7d --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/static/middlebg.png diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pylons.css b/docs/tutorials/wiki/src/views/tutorial/static/pylons.css index c153be07f..fd1914d8d 100644 --- a/docs/tutorials/wiki/src/views/tutorial/static/pylons.css +++ b/docs/tutorials/wiki/src/views/tutorial/static/pylons.css @@ -4,34 +4,23 @@ 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;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} @@ -42,23 +31,26 @@ 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;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#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;} @@ -67,7 +59,7 @@ 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=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pyramid-small.png b/docs/tutorials/wiki/src/views/tutorial/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/static/pyramid-small.png diff --git a/docs/tutorials/wiki/src/views/tutorial/static/pyramid.png b/docs/tutorials/wiki/src/views/tutorial/static/pyramid.png Binary files differnew file mode 100644 index 000000000..347e05549 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/static/pyramid.png diff --git a/docs/tutorials/wiki/src/views/tutorial/static/style.css b/docs/tutorials/wiki/src/views/tutorial/static/style.css deleted file mode 100644 index cad87e0d4..000000000 --- a/docs/tutorials/wiki/src/views/tutorial/static/style.css +++ /dev/null @@ -1,109 +0,0 @@ -html, body { - color: black; - background-color: #ddd; - font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, sans-serif; - margin: 0; - padding: 0; -} - -td, th {padding:3px;border:none;} -tr th {text-align:left;background-color:#f0f0f0;color:#333;} -tr.odd td {background-color:#edf3fe;} -tr.even td {background-color:#fff;} - -#header { - height: 80px; - width: 777px; - background: blue URL('../images/header_inner.png') no-repeat; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - margin: 0 auto 0 auto; -} - -a.link, a, a.active { - color: #369; -} - - -#main_content { - color: black; - font-size: 127%; - background-color: white; - width: 757px; - margin: 0 auto 0 auto; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - padding: 10px; -} - -#sidebar { - border: 1px solid #aaa; - background-color: #eee; - margin: 0.5em; - padding: 1em; - float: right; - width: 200px; - font-size: 88%; -} - -#sidebar h2 { - margin-top: 0; -} - -#sidebar ul { - margin-left: 1.5em; - padding-left: 0; -} - -h1,h2,h3,h4,h5,h6,#getting_started_steps { - font-family: "Century Schoolbook L", Georgia, serif; - font-weight: bold; -} - -h2 { - font-size: 150%; -} - -#footer { - border: 1px solid #aaa; - border-top: 0px none; - color: #999; - background-color: white; - padding: 10px; - font-size: 80%; - text-align: center; - width: 757px; - margin: 0 auto 1em auto; -} - -.code { - font-family: monospace; -} - -span.code { - font-weight: bold; - background: #eee; -} - -#status_block { - margin: 0 auto 0.5em auto; - padding: 15px 10px 15px 55px; - background: #cec URL('../images/ok.png') left center no-repeat; - border: 1px solid #9c9; - width: 450px; - font-size: 120%; - font-weight: bolder; -} - -.notice { - margin: 0.5em auto 0.5em auto; - padding: 15px 10px 15px 55px; - width: 450px; - background: #eef URL('../images/info.png') left center no-repeat; - border: 1px solid #cce; -} - -.fielderror { - color: red; - font-weight: bold; -} diff --git a/docs/tutorials/wiki/src/views/tutorial/static/transparent.gif b/docs/tutorials/wiki/src/views/tutorial/static/transparent.gif Binary files differnew file mode 100644 index 000000000..0341802e5 --- /dev/null +++ b/docs/tutorials/wiki/src/views/tutorial/static/transparent.gif diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt index 1d40f526d..6dbb0edde 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt @@ -1,32 +1,58 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki) - Editing: ${page.__name__}</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <title>${page.__name__} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/ie6.css')}" + type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> - <body> - -<div class="main_content"> - <div style="float:right; width: 10em;"> Viewing - <span tal:replace="page.__name__">Page Name Goes Here</span> <br/> - You can return to the <a href="${request.application_url}" - >FrontPage</a>. - </div> - - <div> - <form action="${save_url}" method="post"> - <textarea name="body" tal:content="page.data" rows="10" cols="60"/> - <input type="submit" name="form.submitted" value="Save"/> - </form> + <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"> + Editing <b><span tal:replace="page.__name__">Page Name Goes + Here</span></b><br/> + You can return to the + <a href="${request.application_url}">FrontPage</a>.<br/> + </div> + <div id="right" class="app-welcome align-right"></div> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <form action="${save_url}" method="post"> + <textarea name="body" tal:content="page.data" rows="10" + cols="60"/><br/> + <input type="submit" name="form.submitted" value="Save"/> + </form> + </div> + </div> + </div> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> </div> -</div> </body> </html> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt index a5a0dd214..d98420680 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt @@ -1,69 +1,75 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.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&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> <body> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <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/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 id="footer"> - <div class="footer">© Copyright 2008-2010, Agendaless Consulting.</div> - </div> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Go" /> + </form> + </div> + <div id="right" class="align-left"> + <h2>Pyramid links</h2> + <ul class="links"> + <li> + <a href="http://pylonsproject.org">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt index 50719f9e9..537ae3a15 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt @@ -1,29 +1,61 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>${page.__name__} - Pyramid tutorial wiki - (based on TurboGears 20-Minute Wiki) - </title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <title>${page.__name__} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/ie6.css')}" + type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> - <body> - -<div class="main_content"> -<div style="float:right; width: 10em;"> Viewing -<span tal:replace="page.__name__">Page Name Goes Here</span> <br/> -You can return to the -<a href="${request.application_url}">FrontPage</a>. -</div> - -<div tal:replace="structure content">Page text goes here.</div> -<p><a tal:attributes="href edit_url" href="">Edit this page</a></p> -</div> - -</body></html> + <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"> + Viewing <b><span tal:replace="page.__name__">Page Name Goes + Here</span></b><br/> + You can return to the + <a href="${request.application_url}">FrontPage</a>.<br/> + </div> + <div id="right" class="app-welcome align-right"></div> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div tal:replace="structure content"> + Page text goes here. + </div> + <p> + <a tal:attributes="href edit_url" href=""> + Edit this page + </a> + </p> + </div> + </div> + </div> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> + </div> +</body> +</html> diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py index c96bc2e9c..42420f2fe 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views.py +++ b/docs/tutorials/wiki/src/views/tutorial/views.py @@ -12,7 +12,7 @@ wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") @view_config(context='tutorial.models.Wiki') def view_wiki(context, request): - return HTTPFound(location = resource_url(context, request, 'FrontPage')) + return HTTPFound(location=resource_url(context, request, 'FrontPage')) @view_config(context='tutorial.models.Page', renderer='tutorial:templates/view.pt') diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 8d30ab807..64cab30db 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -27,43 +27,35 @@ Adding A Root Factory ~~~~~~~~~~~~~~~~~~~~~ We're going to start to use a custom :term:`root factory` within our -``__init__.py`` file. The objects generated by the root factory will be -used as the :term:`context` of each request to our application. In -order for :app:`Pyramid` declarative security to work properly, the -context object generated during a request must be decorated with -security declarations; when we begin to use a custom root factory to -generate our contexts, we can begin to make use of the declarative -security features of :app:`Pyramid`. +``__init__.py`` file. The objects generated by the root factory will be used +as the :term:`context` of each request to our application. We do this to +allow :app:`Pyramid` declarative security to work properly. The context +object generated by the root factory during a request will be decorated with +security declarations. When we begin to use a custom root factory to generate +our contexts, we can begin to make use of the declarative security features +of :app:`Pyramid`. We'll modify our ``__init__.py``, passing in a :term:`root factory` to our :term:`Configurator` constructor. We'll point it at a new class we create inside our ``models.py`` file. Add the following statements to your ``models.py`` file: -.. code-block:: python +.. literalinclude:: src/authorization/tutorial/models.py + :lines: 3-4,45-49 + :linenos: + :language: python - from pyramid.security import Allow - from pyramid.security import Everyone - - class RootFactory(object): - __acl__ = [ (Allow, Everyone, 'view'), - (Allow, 'group:editors', 'edit') ] - def __init__(self, request): - pass - -The ``RootFactory`` class we've just added will be used by -:app:`Pyramid` to construct a ``context`` object. The context is -attached to the request object passed to our view callables as the -``context`` attribute. - -All of our context objects will possess an ``__acl__`` attribute that -allows :data:`pyramid.security.Everyone` (a special principal) to -view all pages, while allowing only a :term:`principal` named -``group:editors`` to edit and add pages. The ``__acl__`` attribute -attached to a context is interpreted specially by :app:`Pyramid` as -an access control list during view callable execution. See -:ref:`assigning_acls` for more information about what an :term:`ACL` -represents. +The ``RootFactory`` class we've just added will be used by :app:`Pyramid` to +construct a ``context`` object. The context is attached to the request +object passed to our view callables as the ``context`` attribute. + +The context object generated by our root factory will possess an ``__acl__`` +attribute that allows :data:`pyramid.security.Everyone` (a special principal) +to view all pages, while allowing only a :term:`principal` named +``group:editors`` to edit and add pages. The ``__acl__`` attribute attached +to a context is interpreted specially by :app:`Pyramid` as an access control +list during view callable execution. See :ref:`assigning_acls` for more +information about what an :term:`ACL` represents. .. 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 @@ -84,30 +76,10 @@ For any :app:`Pyramid` application to perform authorization, we need to add a We'll change our ``__init__.py`` file to enable an ``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable -declarative security checking. We'll also change ``__init__.py`` to add a -:meth:`pyramid.config.Configurator.add_view` call to points at our -``login`` :term:`view callable`, also known as a :term:`forbidden view`. -This configures our newly created login view to show up when :app:`Pyramid` -detects that a view invocation can not be authorized. Also, we'll add -``view_permission`` arguments with the value ``edit`` to the ``edit_page`` -and ``add_page`` routes. This indicates that the view callables which these -routes reference cannot be invoked without the authenticated user possessing -the ``edit`` permission with respect to the current context. - -This makes the assertion that only users who possess the effective ``edit`` -permission at the time of the request may invoke those two views. We've -granted the ``group:editors`` principal the ``edit`` permission at the root -model via its ACL, so only the a user whom is a member of the group named -``group:editors`` will able to invoke the views associated with the -``add_page`` or ``edit_page`` routes. - -Viewing Your Changes -~~~~~~~~~~~~~~~~~~~~ - -When we're done configuring a root factory, adding an authorization policy, -and adding views, your application's ``__init__.py`` will look like this: +declarative security checking. .. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 15-21 :linenos: :language: python @@ -120,12 +92,52 @@ representing a :term:`dotted Python name`, which points at the ``groupfinder`` function in the current directory's ``security.py`` file. We haven't added that module yet, but we're about to. +We'll also change ``__init__.py``, adding a call to +:meth:`pyramid.config.Configurator.add_view` that points at our ``login`` +:term:`view callable`. This is also known as a :term:`forbidden view`: + +.. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 24-26 + :linenos: + :language: python + +A forbidden view configures our newly created login view to show up when +:app:`Pyramid` detects that a view invocation can not be authorized. + +We'll also add ``view_permission`` arguments with the value ``edit`` to the +``edit_page`` and ``add_page`` routes. This indicates that the view +callables which these routes reference cannot be invoked without the +authenticated user possessing the ``edit`` permission with respect to the +current context. + +.. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 32-39 + :linenos: + :language: python + +Adding these ``view_permission`` arguments causes Pyramid to make the +assertion that only users who possess the effective ``edit`` permission at +the time of the request may invoke those two views. We've granted the +``group:editors`` principal the ``edit`` permission at the root model via its +ACL, so only the a user whom is a member of the group named ``group:editors`` +will able to invoke the views associated with the ``add_page`` or +``edit_page`` routes. + +Viewing Your Changes +~~~~~~~~~~~~~~~~~~~~ + +When we're done configuring a root factory, adding an authorization policy, +and adding views, your application's ``__init__.py`` will look like this: + +.. literalinclude:: src/authorization/tutorial/__init__.py + :linenos: + :language: python Adding ``security.py`` ~~~~~~~~~~~~~~~~~~~~~~ Add a ``security.py`` module within your package (in the same directory as -"__init__.py", "views.py", etc) with the following content: +:file:`__init__.py`, :file:`views.py`, etc) with the following content: .. literalinclude:: src/authorization/tutorial/security.py :linenos: @@ -161,7 +173,7 @@ provide a link to it. This view will clear the credentials of the logged in user and redirect back to the front page. We'll add a different file (for presentation convenience) to add login -and logout view callables. Add a file named ``login.py`` to your +and the logout view callables. Add a file named ``login.py`` to your application (in the same directory as ``views.py``) with the following content: @@ -173,8 +185,8 @@ Changing Existing Views ~~~~~~~~~~~~~~~~~~~~~~~ Then we need to change each of our ``view_page``, ``edit_page`` and -``add_page`` views in ``views.py`` to pass a "logged in" parameter to -its template. We'll add something like this to each view body: +``add_page`` views in ``views.py`` to pass a "logged in" parameter to its +template. We'll add something like this to each view body: .. ignore-next-block .. code-block:: python @@ -183,8 +195,8 @@ its template. We'll add something like this to each view body: from pyramid.security import authenticated_userid logged_in = authenticated_userid(request) -We'll then change the return value of these views to pass the -`resulting `logged_in`` value to the template, e.g.: +We'll then change the return value of these views to pass the `resulting +`logged_in`` value to the template, e.g.: .. ignore-next-block .. code-block:: python @@ -202,7 +214,6 @@ Add a ``login.pt`` template to your templates directory. It's referred to within the login view we just added to ``login.py``. .. literalinclude:: src/authorization/tutorial/templates/login.pt - :linenos: :language: xml Change ``view.pt`` and ``edit.pt`` @@ -212,11 +223,10 @@ We'll also need to change our ``edit.pt`` and ``view.pt`` templates to display a "Logout" link if someone is logged in. This link will invoke the logout view. -To do so we'll add this to both templates within the ``<div -class="main_content">`` div: +To do so we'll add this to both templates within the ``<div id="right" +class="app-welcome align-right">`` div: .. code-block:: xml - :linenos: <span tal:condition="logged_in"> <a href="${request.application_url}/logout">Logout</a> @@ -261,13 +271,11 @@ Our ``views.py`` module will look something like this when we're done: Our ``edit.pt`` template will look something like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt - :linenos: :language: xml Our ``view.pt`` template will look something like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/view.pt - :linenos: :language: xml Revisiting the Application @@ -279,5 +287,3 @@ of hitting an edit or add page and submitting the login form with the hand corner. When we click it, we're logged out, and redirected back to the front page. - - diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 565bd0e96..4d3496788 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -2,10 +2,9 @@ Basic Layout ============ -The starter files generated by the ``pyramid_routesalchemy`` template -are basic, but they provide a good orientation for the high-level -patterns common to most :term:`url dispatch` -based :app:`Pyramid` -projects. +The starter files generated by the ``pyramid_routesalchemy`` template are +basic, but they provide a good orientation for the high-level patterns common +to most :term:`url dispatch` -based :app:`Pyramid` projects. The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/basiclayout/ @@ -23,58 +22,94 @@ The generated ``development.ini`` file is read by ``paster`` which looks for the application module in the ``use`` variable of the ``app:tutorial`` section. The *entry point* is defined in the Setuptools configuration of this module, specifically in the ``setup.py`` file. For this tutorial, the *entry -point* is defined as ``tutorial:main`` and points to the following ``main`` -function: +point* is defined as ``tutorial:main`` and points to a function named ``main``. + +First we need some imports to support later code: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :end-before: main + :linenos: + :language: py + +Next we define the main function and create a SQLAlchemy database engine from +the ``sqlalchemy.`` prefixed settings in the ``development.ini`` file's +``[app:tutorial]`` section. This will be a URI (something like +``sqlite://``): .. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 6-9 :linenos: :language: py -#. *Lines 1-4*. Imports to support later code. - -#. *Line 9*. Create a SQLAlchemy database engine from the ``sqlalchemy.`` - prefixed settings in the ``development.ini`` file's ``[app:tutorial]`` - section. This will be a URI (something like ``sqlite://``). - -#. *Line 10*. We initialize our SQL database using SQLAlchemy, passing - it the engine - -#. *Line 11*. We construct a :term:`Configurator`. ``settings`` is - passed as a keyword argument with the dictionary values passed by - PasteDeploy as the ``settings`` argument. This will be a - dictionary of settings parsed by PasteDeploy, which contains - deployment-related values such as ``reload_templates``, - ``db_string``, etc. - -#. *Line 12*. We call - :meth:`pyramid.config.Configurator.add_static_view` with the - arguments ``static`` (the name), and ``tutorial:static`` (the path). This - registers a static resource view which will match any URL that starts with - ``/static/``. 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. 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 a static file resource, - such as a CSS file. - -#. *Lines 13-14*. Register 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 an ``pattern`` equalling - ``/`` it is the "default" route. The argument named ``view`` with the - value ``tutorial.views.my_view`` is the dotted name to a *function* we - write (generated by the ``pyramid_routesalchemy`` template) that is given - a ``request`` object and which returns a response or a dictionary. You - will use :meth:`pyramid.config.Configurator.add_route` statements - in a :term:`URL dispatch` based application to map URLs to code. This - route also names a ``view_renderer``, which is a template which lives in - the ``templates`` subdirectory of the package. When the - ``tutorial.views.my_view`` view returns a dictionary, a :term:`renderer` - will use this template to create a response. - -#. *Line 15*. We use the - :meth:`pyramid.config.Configurator.make_wsgi_app` method to return - a :term:`WSGI` application. +We then initialize our SQL database using SQLAlchemy, passing +it the engine: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 10 + :language: py + +The next step is to construct a :term:`Configurator`: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 11 + :language: py + +``settings`` is passed to the Configurator as a keyword argument with the +dictionary values passed by PasteDeploy as the ``**settings`` argument. This +will be a dictionary of settings parsed from the ``.ini`` file, which +contains deployment-related values such as ``reload_templates``, +``db_string``, etc. + +We now can call :meth:`pyramid.config.Configurator.add_static_view` with the +arguments ``static`` (the name), and ``tutorial:static`` (the path): + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 12 + :language: py + +This registers a static resource view which will match any URL that starts with +``/static/``. 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. 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 a static file resource, +such as a CSS file. + +Using the configurator we can also register a :term:`route configuration` +via the :meth:`pyramid.config.Configurator.add_route` method that will be +used when the URL is ``/``: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 13-14 + :language: py + +Since this route has a ``pattern`` equalling ``/`` it is the route that will +be called when the URL ``/`` is visted, e.g. ``http://localhost:6543/``. The +argument named ``view`` with the value ``tutorial.views.my_view`` is the +dotted name to a *function* we write (generated by the +``pyramid_routesalchemy`` template) that is given a ``request`` object and +which returns a response or a dictionary. + +You will use :meth:`pyramid.config.Configurator.add_route` statements in a +:term:`URL dispatch` based application to map URLs to code. This route also +names a ``view_renderer``, which is a template which lives in the +``templates`` subdirectory of the package. When the +``tutorial.views.my_view`` view returns a dictionary, a :term:`renderer` will +use this template to create a response. + +Fimnally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app` +method to return a :term:`WSGI` application: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :lines: 15 + :language: py + +Our final ``__init__.py`` file will look like this: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :linenos: + :language: py Content Models with ``models.py`` --------------------------------- @@ -85,34 +120,64 @@ SQLAlchemy is an "object relational mapper" (an ORM). The ``models.py`` file is where the ``pyramid_routesalchemy`` Paster template put the classes that implement our models. -Here is the source for ``models.py``: +Let's take a look. First, we need some imports to support later code. .. literalinclude:: src/basiclayout/tutorial/models.py + :end-before: DBSession :linenos: :language: py -#. *Lines 1-13*. Imports to support later code. +Next we set up a SQLAlchemy "DBSession" object: + + .. literalinclude:: src/basiclayout/tutorial/models.py + :lines: 15-16 + :linenos: + :language: py -#. *Line 15*. We set up a SQLAlchemy "DBSession" object here. We - specify that we'd like to use the "ZopeTransactionExtension". This - extension is an extension which allows us to use a *transaction - manager* instead of controlling commits and aborts to database - operations by hand. +We also need to create a declarative ``Base`` object to use as a +base class for our model: -#. *Line 16*. We create a declarative ``Base`` object to use as a - base class for our model. + .. literalinclude:: src/basiclayout/tutorial/models.py + :lines: 17 + :language: py + +To give a simple example of a model class, we define one named ``MyModel``: + + .. literalinclude:: src/basiclayout/tutorial/models.py + :pyobject: MyModel + :linenos: + :language: py -#. *Lines 18-26*. A model class named ``MyModel``. It has an - ``__init__`` that takes a two arguments (``name``, and ``value``). - It stores these values as ``self.name`` and ``self.value`` within - the ``__init__`` function itself. The ``MyModel`` class also has a - ``__tablename__`` attribute. This informs SQLAlchemy which table - to use to store the data representing instances of this class. +Our sample model has an ``__init__`` that takes a two arguments (``name``, +and ``value``). It stores these values as ``self.name`` and ``self.value`` +within the ``__init__`` function itself. The ``MyModel`` class also has a +``__tablename__`` attribute. This informs SQLAlchemy which table to use to +store the data representing instances of this class. -#. *Lines 28-33*. A function named ``populate`` which adds a single - model instance into our SQL storage and commits a transaction. +Next we define a function named ``populate`` which adds a single +model instance into our SQL storage and commits a transaction: -#. *Lines 35-42*. A function named ``initialize_sql`` which receives a SQL - database engine and binds it to our SQLAlchemy DBSession object. It also - calls the ``populate`` function, to do initial database population. + .. literalinclude:: src/basiclayout/tutorial/models.py + :pyobject: populate + :linenos: + :language: py + +The function doesn't do a lot in this case, but it's there to illustrate +how an application requiring many objects to be set up could work. + +Lastly we have a function named ``initialize_sql`` which receives a SQL +database engine and binds it to our SQLAlchemy DBSession object. It also +calls the ``populate`` function, to do initial database population. This +is the initialization function that is called from __init__.py above. + + .. literalinclude:: src/basiclayout/tutorial/models.py + :pyobject: initialize_sql + :linenos: + :language: py + +Here is the complete source for ``models.py``: + + .. literalinclude:: src/basiclayout/tutorial/models.py + :linenos: + :language: py diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 09e1f26c3..7e8555190 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -31,28 +31,39 @@ application, this class should inherit from an instance of :class:`sqlalchemy.ext.declarative.declarative_base`. Declarative SQLAlchemy models are easier to use than directly-mapped ones. -Our ``Page`` class will have 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``, ``pagename`` and ``data`` (all instances -of :class:`sqlalchemy.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. - -We'll also remove our ``populate`` function. We'll inline the -populate step into ``initialize_sql``, changing our ``initialize_sql`` -function to add a FrontPage object to our database at startup time. -We're also going to use slightly different binding syntax. It will -will otherwise largely be the same as the ``initialize_sql`` in the -paster-generated ``models.py``. +.. literalinclude:: src/models/tutorial/models.py + :pyobject: Page + :linenos: + :language: python + +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``, ``pagename`` and +``data`` (all instances of :class:`sqlalchemy.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. + +We'll also remove our ``populate`` function. We'll inline the populate step +into ``initialize_sql``, changing our ``initialize_sql`` function to add a +FrontPage object to our database at startup time. + +.. literalinclude:: src/models/tutorial/models.py + :pyobject: initialize_sql + :linenos: + :language: python + +Here, we're using a slightly different binding syntax. It is otherwise +largely the same as the ``initialize_sql`` in the paster-generated +``models.py``. Our DBSession assignment stays the same as the original generated ``models.py``. -Looking at the Result of Our Edits to ``models.py`` ---------------------------------------------------- +Looking at the Result of all Our Edits to ``models.py`` +------------------------------------------------------- The result of all of our edits to ``models.py`` will end up looking something like this: @@ -64,11 +75,10 @@ something like this: Viewing the Application in a Browser ------------------------------------ -We can't. At this point, our system is in a "non-runnable" state; -we'll need to change view-related files in the next chapter to be able -to start the application successfully. If you try to start the -application, you'll wind up with a Python traceback on your console -that ends with this exception: +We can't. At this point, our system is in a "non-runnable" state; we'll need +to change view-related files in the next chapter to be able to start the +application successfully. If you try to start the application, you'll wind +up with a Python traceback on your console that ends with this exception: .. code-block:: text diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index e3d611136..874c49d4c 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -2,10 +2,10 @@ Defining Views ============== -A :term:`view callable` in a :term:`url dispatch` -based -: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. +A :term:`view callable` in a :term:`url dispatch` -based :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. .. note:: A :app:`Pyramid` view can also be defined as callable which accepts *two* arguments: a :term:`context` and a @@ -23,11 +23,11 @@ assumed to return a :term:`response` object. The request passed to every view that is called as the result of a route match has an attribute named ``matchdict`` that contains the elements placed into the URL by the ``pattern`` of a ``route`` statement. For instance, if a -call to :meth:`pyramid.config.Configurator.add_route` in -``__init__.py`` had the pattern ``{one}/{two}``, and the URL at -``http://example.com/foo/bar`` was invoked, matching this pattern, the -matchdict dictionary attached to the request passed to the view would have a -``one`` key with the value ``foo`` and a ``two`` key with the value ``bar``. +call to :meth:`pyramid.config.Configurator.add_route` in ``__init__.py`` had +the pattern ``{one}/{two}``, and the URL at ``http://example.com/foo/bar`` +was invoked, matching this pattern, the matchdict dictionary attached to the +request passed to the view would have a ``one`` key with the value ``foo`` +and a ``two`` key with the value ``bar``. The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/ @@ -36,13 +36,13 @@ The source code for this tutorial stage can be browsed at Declaring Dependencies in Our ``setup.py`` File =============================================== -The view code in our application will depend on a package which is not -a dependency of the original "tutorial" application. The original -"tutorial" application was generated by the ``paster create`` command; -it doesn't know about our custom application requirements. We need to -add a dependency on the ``docutils`` package to our ``tutorial`` -package's ``setup.py`` file by assigning this dependency to the -``install_requires`` parameter in the ``setup`` function. +The view code in our application will depend on a package which is not a +dependency of the original "tutorial" application. The original "tutorial" +application was generated by the ``paster create`` command; it doesn't know +about our custom application requirements. We need to add a dependency on +the ``docutils`` package to our ``tutorial`` package's ``setup.py`` file by +assigning this dependency to the ``install_requires`` parameter in the +``setup`` function. Our resulting ``setup.py`` should look like so: @@ -58,38 +58,43 @@ Our resulting ``setup.py`` should look like so: Adding View Functions ===================== -We'll get rid of our ``my_view`` view function in our ``views.py`` -file. It's only an example and isn't relevant to our application. +We'll get rid of our ``my_view`` view function in our ``views.py`` file. +It's only an example and isn't relevant to our application. Then we're going to add four :term:`view callable` functions to our -``views.py`` module. One view callable (named ``view_wiki``) will -display the wiki itself (it will answer on the root URL), another -named ``view_page`` will display an individual page, another named -``add_page`` will allow a page to be added, and a final view callable -named ``edit_page`` will allow a page to be edited. We'll describe -each one briefly and show the resulting ``views.py`` file afterward. +``views.py`` module. One view callable (named ``view_wiki``) will display +the wiki itself (it will answer on the root URL), another named ``view_page`` +will display an individual page, another named ``add_page`` will allow a page +to be added, and a final view callable named ``edit_page`` will allow a page +to be edited. We'll describe each one briefly and show the resulting +``views.py`` file afterward. .. note:: - There is nothing special about the filename ``views.py``. A project - may have many view callables throughout its codebase in - arbitrarily-named files. Files implementing view callables often - have ``view`` in their filenames (or may live in a Python subpackage - of your application package named ``views``), but this is only by - convention. + There is nothing special about the filename ``views.py``. A project may + have many view callables throughout its codebase in arbitrarily-named + files. Files 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. The ``view_wiki`` view function ------------------------------- -The ``view_wiki`` function will respond as the :term:`default view` of -a ``Wiki`` model object. It always redirects to a URL which -represents the path to our "FrontPage". It returns an instance of the -:class:`pyramid.httpexceptions.HTTPFound` class (instances of which -implement the WebOb :term:`response` interface), It will use the -:func:`pyramid.url.route_url` API to construct a URL to the -``FrontPage`` page (e.g. ``http://localhost:6543/FrontPage``), and -will use it as the "location" of the HTTPFound response, forming an -HTTP redirect. +The ``view_wiki`` function will respond as the :term:`default view` of a +``Wiki`` model object. It always redirects to a URL which represents the +path to our "FrontPage". + +.. literalinclude:: src/views/tutorial/views.py + :pyobject: view_wiki + :linenos: + :language: python + +The ``view_wiki`` function returns an instance of the +:class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement +the WebOb :term:`response` interface), It will use the +:func:`pyramid.url.route_url` API to construct a URL to the ``FrontPage`` +page (e.g. ``http://localhost:6543/FrontPage``), and will use it as the +"location" of the HTTPFound response, forming an HTTP redirect. The ``view_page`` view function ------------------------------- @@ -101,88 +106,95 @@ attribute of a Page object) as HTML. Then it substitutes an HTML anchor for each *WikiWord* reference in the rendered HTML using a compiled regular expression. +.. literalinclude:: src/views/tutorial/views.py + :pyobject: view_page + :linenos: + :language: python + The curried function named ``check`` is used as the first argument to -``wikiwords.sub``, indicating that it should be called to provide a -value for each WikiWord match found in the content. If the wiki -already contains a page with the matched WikiWord name, the ``check`` -function generates a view link to be used as the substitution value -and returns it. If the wiki does not already contain a page with with -the matched WikiWord name, the function generates an "add" link as the -substitution value and returns it. - -As a result, the ``content`` variable is now a fully formed bit of -HTML containing various view and add links for WikiWords based on the -content of our current page object. - -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 this view 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 template. In our case, the template which will be -rendered will be the ``templates/view.pt`` template, as per the -configuration put into effect in ``__init__.py``. +``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, the ``check`` function generates a view +link to be used as the substitution value and returns it. If the wiki does +not already contain a page with with the matched WikiWord name, the function +generates an "add" link as the substitution value and returns it. + +As a result, the ``content`` variable is now a fully formed bit of HTML +containing various view and add links for WikiWords based on the content of +our current page object. + +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 this view 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 template. In our case, +the template which will be rendered will be the ``templates/view.pt`` +template, as per the configuration put into effect in ``__init__.py``. The ``add_page`` view function ------------------------------ -The ``add_page`` function will be invoked when a user clicks on a -*WikiWord* which isn't yet represented as a page in the system. The -``check`` function within the ``view_page`` view generates URLs to -this view. It also acts as a handler for the form that is generated -when we want to add a page 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 ``add_page`` function will be invoked when a user clicks on a *WikiWord* +which isn't yet represented as a page in the system. The ``check`` function +within the ``view_page`` view generates URLs to this view. It also acts as a +handler for the form that is generated when we want to add a page 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. + +.. literalinclude:: src/views/tutorial/views.py + :pyobject: add_page + :linenos: + :language: python -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 ``pagename`` -value in the matchdict will be ``SomeName``. +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 ``pagename`` value in +the matchdict will be ``SomeName``. If the view execution is *not* a result of a form submission (if 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 use as the form post URL during rendering. We're -lazy here, so we're trying to use the same template -(``templates/edit.pt``) for the add view as well as the page edit -view, so we create a dummy Page object in order to satisfy the edit -form's desire to have *some* page object exposed as ``page``, and -:app:`Pyramid` will render the template associated with this view -to a response. - -If the view execution *is* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``True``), we -scrape the page body from the form data, create a Page object using -the name in the matchdict ``pagename``, and obtain the page body from -the request, and save it into the database using ``session.add``. We -then redirect back to the ``view_page`` view (the :term:`default view` -for a Page) for the newly created page. +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 use as the form post URL during rendering. We're lazy here, so +we're trying to use the same template (``templates/edit.pt``) for the add +view as well as the page edit view, so we create a dummy Page object in order +to satisfy the edit form's desire to have *some* page object exposed as +``page``, and :app:`Pyramid` will render the template associated with this +view to a response. + +If the view execution *is* a result of a form submission (if the expression +``'form.submitted' in request.params`` is ``True``), we scrape the page body +from the form data, create a Page object using the name in the matchdict +``pagename``, and obtain the page body from the request, and save it into the +database using ``session.add``. We then redirect back to the ``view_page`` +view (the :term:`default view` for a Page) for the newly created page. The ``edit_page`` view function ------------------------------- -The ``edit_page`` function will be invoked when a user clicks the -"Edit this Page" button on the view form. It renders an edit form but -it also acts as the handler for the form it renders. The -``matchdict`` attribute of the request passed to the ``add_page`` view -will have a ``pagename`` key matching the name of the page the user -wants to edit. +The ``edit_page`` function will be invoked when a user clicks the "Edit this +Page" button on the view form. It renders an edit form but it also acts as +the 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. + +.. literalinclude:: src/views/tutorial/views.py + :pyobject: edit_page + :linenos: + :language: python If the view execution is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the -view simply renders the edit form, passing the request, the page -object, and a save_url which will be used as the action of the -generated form. - -If the view execution *is* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``True``), the -view grabs the ``body`` element of the request parameter and sets it -as the ``data`` key in the matchdict. It then redirects to the -default view of the wiki page, which will always be the ``view_page`` -view. - -Viewing the Result of Our Edits to ``views.py`` -=============================================== +expression ``'form.submitted' in request.params`` is ``False``), the view +simply renders the edit form, passing the request, the page object, and a +save_url which will be used as the action of the generated form. + +If the view execution *is* a result of a form submission (if the expression +``'form.submitted' in request.params`` is ``True``), the view grabs the +``body`` element of the request parameter and sets it as the ``data`` +attribute of the page object. It then redirects to the default view of the +wiki page, which will always be the ``view_page`` view. + +Viewing the Result of all Our Edits to ``views.py`` +=================================================== The result of all of our edits to ``views.py`` will leave it looking like this: @@ -201,18 +213,16 @@ The views we've added all reference a :term:`template`. Each template is a The ``view.pt`` Template ------------------------ -The ``view.pt`` template is used for viewing a single wiki page. It -is used by the ``view_page`` view function. It should have a div that -is "structure replaced" with the ``content`` value provided by the -view. It should also have a link on the rendered page that points at -the "edit" URL (the URL which invokes the ``edit_page`` view for the -page being viewed). +The ``view.pt`` template is used for viewing a single wiki page. It is used +by the ``view_page`` view function. It should have a div that is "structure +replaced" with the ``content`` value provided by the view. It should also +have a link on the rendered page that points at the "edit" URL (the URL which +invokes the ``edit_page`` view for the page being viewed). -Once we're done with the ``view.pt`` template, it will look a lot like -the below: +Once we're done with the ``view.pt`` template, it will look a lot like the +below: .. literalinclude:: src/views/tutorial/templates/view.pt - :linenos: :language: xml .. note:: The names available for our use in a template are always @@ -228,37 +238,35 @@ the below: The ``edit.pt`` Template ------------------------ -The ``edit.pt`` template is used for adding and editing a wiki page. -It is used by the ``add_page`` and ``edit_page`` view functions. It -should display a page containing a form that POSTs back to the -"save_url" argument supplied by the view. The form should have a -"body" textarea field (the page data), and a submit button that has -the name "form.submitted". The textarea in the form should be filled -with any existing page data when it is rendered. +The ``edit.pt`` template is used for adding and editing a wiki page. It is +used by the ``add_page`` and ``edit_page`` view functions. It should display +a page containing a form that POSTs back to the "save_url" argument supplied +by the view. The form should have a "body" textarea field (the page data), +and a submit button that has the name "form.submitted". The textarea in the +form should be filled with any existing page data when it is rendered. Once we're done with the ``edit.pt`` template, it will look a lot like the below: .. literalinclude:: src/views/tutorial/templates/edit.pt - :linenos: :language: xml -Static Resources ----------------- - -Our templates name a single static resource named ``style.css``. We need to -create this and place it in a file named ``style.css`` within our package's -``static`` directory. This file is a little too long to replicate within the -body of this guide, however it is available `online -<http://github.com/Pylons/pyramid/blob/master/docs/tutorials/wiki2/src/views/tutorial/static/style.css>`_. +Static Assets +------------- +Our templates name a single static asset named ``pylons.css``. We don't need +to create this file within our package's ``static`` directory because it was +provided at the time we created the project. This file is a little too long +to replicate within the body of this guide, however it is available `online +<http://github.com/Pylons/pyramid/blob/master/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css>`_. This CSS file will be accessed via -e.g. ``http://localhost:6543/static/style.css`` by virtue of the call we've -made to :meth:`pyramid.config.Configurator.add_static_view` within our -``__init__.py`` file. Any number and type of static resources can be placed -in this directory (or subdirectories) and are just referred to by URL within -templates. +e.g. ``http://localhost:6543/static/pylons.css`` by virtue of the call to +``add_static_view`` directive we've made in the ``__init__`` 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. Mapping Views to URLs in ``__init__.py`` ======================================== diff --git a/docs/tutorials/wiki2/distributing.rst b/docs/tutorials/wiki2/distributing.rst index f4421e145..c80b43337 100644 --- a/docs/tutorials/wiki2/distributing.rst +++ b/docs/tutorials/wiki2/distributing.rst @@ -18,14 +18,7 @@ On Windows: .. code-block:: text - c:\bigfntut> ..\Scripts\python setup.py sdist - -.. warning:: If your project files are not checked in to a version - control repository (such as Subversion), the dist tarball will - *not* contain all the files it needs to. In particular, it will - not contain non-Python-source files (such as templates and static - files). To ensure that these are included, check your files into a - version control repository before running ``setup.py sdist``. + c:\pyramidtut> ..\Scripts\python setup.py sdist The output of such a command will be something like: diff --git a/docs/tutorials/wiki2/index.rst b/docs/tutorials/wiki2/index.rst index 03b405048..1aff949b9 100644 --- a/docs/tutorials/wiki2/index.rst +++ b/docs/tutorials/wiki2/index.rst @@ -1,7 +1,7 @@ .. _bfg_sql_wiki_tutorial: -SQLAlchemy + URL Dispatch Wiki Tutorial (For Developers Familiar with Pylons 1) -=============================================================================== +SQLAlchemy + URL Dispatch Wiki Tutorial +======================================= This tutorial introduces a :term:`SQLAlchemy` and :term:`url dispatch` -based :app:`Pyramid` application to a developer familiar with Python, and will be diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index e6fd5e6b9..ed81e3774 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -2,10 +2,9 @@ Installation ============ -For the most part, the installation process for this tutorial -duplicates the steps described in :ref:`installing_chapter` and -:ref:`project_narr`, however it also explains how to install -additional libraries for tutorial purposes. +This tutorial assumes that Python and virtualenv are already installed +and working in your system. If you need help setting this up, you should +refer to the chapters on :ref:`installing_chapter`. Preparation =========== @@ -22,39 +21,17 @@ Preparation, UNIX manager. For example, on a Debian Linux system, do ``sudo apt-get install libsqlite3-dev``. -#. If you don't already have a Python 2.6 interpreter installed on - your system, obtain, install, or find `Python 2.6 - <http://www.python.org/download/releases/2.6.6/>`_ for your system. - -#. Install the latest `setuptools` into the Python you - obtained/installed/found in the step above: download `ez_setup.py - <http://peak.telecommunity.com/dist/ez_setup.py>`_ and run it using - the ``python`` interpreter of your Python 2.6 installation: - - .. code-block:: text - - $ /path/to/my/Python-2.6/bin/python ez_setup.py - -#. Use that Python's `bin/easy_install` to install `virtualenv`: +#. Use your Python's virtualenv to make a workspace: .. code-block:: text - $ /path/to/my/Python-2.6/bin/easy_install virtualenv + $ path/to/my/Python-2.6/bin/virtualenv --no-site-packages pyramidtut -#. Use that Python's virtualenv to make a workspace: +#. Switch to the ``pyramidtut`` directory: .. code-block:: text - $ path/to/my/Python-2.6/bin/virtualenv --no-site-packages bigfntut - -#. Switch to the ``bigfntut`` directory: - - .. code-block:: text - - $ cd bigfntut - -#. (Optional) Consider using ``source bin/activate`` to make your - shell environment wired to use the virtualenv. + $ cd pyramidtut #. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies installed: @@ -73,52 +50,30 @@ Preparation, UNIX Preparation, Windows -------------------- -#. Install, or find `Python 2.6.6 - <http://python.org/download/releases/2.6.6/>`_ for your system. - -#. Install the latest `setuptools` into the Python you - obtained/installed/found in the step above: download `ez_setup.py - <http://peak.telecommunity.com/dist/ez_setup.py>`_ and run it using - the ``python`` interpreter of your Python 2.6 installation using a - command prompt: - - .. code-block:: text - - c:\> c:\Python26\python ez_setup.py - -#. Use that Python's `bin/easy_install` to install `virtualenv`: +#. Use your Python's virtualenv to make a workspace: .. code-block:: text - c:\> c:\Python26\Scripts\easy_install virtualenv + c:\> c:\Python26\Scripts\virtualenv --no-site-packages pyramidtut -#. Use that Python's virtualenv to make a workspace: +#. Switch to the ``pyramidtut`` directory: .. code-block:: text - c:\> c:\Python26\Scripts\virtualenv --no-site-packages bigfntut - -#. Switch to the ``bigfntut`` directory: - - .. code-block:: text - - c:\> cd bigfntut - -#. (Optional) Consider using ``bin\activate.bat`` to make your shell - environment wired to use the virtualenv. + c:\> cd pyramidtut #. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies installed: .. code-block:: text - c:\bigfntut> Scripts\easy_install pyramid + c:\pyramidtut> Scripts\easy_install pyramid #. Use ``easy_install`` to install various packages from PyPI. .. code-block:: text - c:\bigfntut> Scripts\easy_install -i docutils \ + c:\pyramidtut> Scripts\easy_install -i docutils \ nose coverage zope.sqlalchemy SQLAlchemy repoze.tm2 @@ -133,7 +88,7 @@ variety of templates to generate sample projects. We will use the that uses :term:`SQLAlchemy` and :term:`URL dispatch`. The below instructions assume your current working directory is the -"virtualenv" named "bigfntut". +"virtualenv" named "pyramidtut". On UNIX: @@ -145,7 +100,7 @@ On Windows: .. code-block:: text - c:\bigfntut> Scripts\paster create -t pyramid_routesalchemy tutorial + c:\pyramidtut> Scripts\paster create -t pyramid_routesalchemy tutorial .. note:: If you are using Windows, the ``pyramid_routesalchemy`` Paster template may not deal gracefully with installation into a @@ -173,8 +128,8 @@ On Windows: .. code-block:: text - c:\bigfntut> cd tutorial - c:\bigfntut\tutorial> ..\Scripts\python setup.py develop + c:\pyramidtut> cd tutorial + c:\pyramidtut\tutorial> ..\Scripts\python setup.py develop .. _sql_running_tests: @@ -194,7 +149,7 @@ On Windows: .. code-block:: text - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q + c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q Starting the Application ======================== @@ -211,7 +166,7 @@ On Windows: .. code-block:: text - c:\bifgfntut\tutorial> ..\Scripts\paster serve development.ini --reload + c:\pyramidtut\tutorial> ..\Scripts\paster serve development.ini --reload Exposing Test Coverage Information ================================== @@ -235,7 +190,7 @@ On Windows: .. code-block:: text - c:\bigfntut\tutorial> ..\Scripts\easy_install nose coverage + c:\pyramidtut\tutorial> ..\Scripts\easy_install nose coverage Once ``nose`` and ``coverage`` are installed, we can actually run the coverage tests. @@ -250,7 +205,7 @@ On Windows: .. code-block:: text - c:\bigfntut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \ + c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \ --cover-erase --with-coverage Looks like our package's ``models`` module doesn't quite have 100% diff --git a/docs/tutorials/wiki2/src/authorization/MANIFEST.in b/docs/tutorials/wiki2/src/authorization/MANIFEST.in new file mode 100644 index 000000000..81beba1b1 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/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/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index b0cfd12a7..3b615f635 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -11,9 +11,13 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] pipeline = egg:WebError#evalerror - egg:repoze.tm2#tm + tm tutorial +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini new file mode 100644 index 000000000..0fdc38811 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/production.ini @@ -0,0 +1,77 @@ +[app:tutorial] +use = egg:tutorial +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en +sqlalchemy.url = sqlite:///%(here)s/tutorial.db + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + +[pipeline:main] +pipeline = + weberror + tm + tutorial + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[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][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py index 3be3146b7..ae9869d50 100644 --- a/docs/tutorials/wiki2/src/authorization/setup.py +++ b/docs/tutorials/wiki2/src/authorization/setup.py @@ -11,10 +11,10 @@ requires = [ 'pyramid', 'SQLAlchemy', 'transaction', - 'repoze.tm2', + 'repoze.tm2>=1.0b1', # default_commit_veto 'zope.sqlalchemy', 'WebError', - 'docutils' + 'docutils', ] if sys.version_info[:3] < (2,5,0): diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/footerbg.png b/docs/tutorials/wiki2/src/authorization/tutorial/static/footerbg.png Binary files differnew file mode 100644 index 000000000..1fbc873da --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/footerbg.png diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/headerbg.png b/docs/tutorials/wiki2/src/authorization/tutorial/static/headerbg.png Binary files differnew file mode 100644 index 000000000..0596f2020 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/headerbg.png diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/ie6.css b/docs/tutorials/wiki2/src/authorization/tutorial/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/logo.png b/docs/tutorials/wiki2/src/authorization/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki2/src/authorization/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/middlebg.png b/docs/tutorials/wiki2/src/authorization/tutorial/static/middlebg.png Binary files differnew file mode 100644 index 000000000..2369cfb7d --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/middlebg.png diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css index c153be07f..fd1914d8d 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/pylons.css @@ -4,34 +4,23 @@ 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;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} @@ -42,23 +31,26 @@ 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;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#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;} @@ -67,7 +59,7 @@ 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=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid-small.png b/docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid-small.png diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid.png b/docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid.png Binary files differnew file mode 100644 index 000000000..347e05549 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid.png diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/style.css b/docs/tutorials/wiki2/src/authorization/tutorial/static/style.css deleted file mode 100644 index cad87e0d4..000000000 --- a/docs/tutorials/wiki2/src/authorization/tutorial/static/style.css +++ /dev/null @@ -1,109 +0,0 @@ -html, body { - color: black; - background-color: #ddd; - font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, sans-serif; - margin: 0; - padding: 0; -} - -td, th {padding:3px;border:none;} -tr th {text-align:left;background-color:#f0f0f0;color:#333;} -tr.odd td {background-color:#edf3fe;} -tr.even td {background-color:#fff;} - -#header { - height: 80px; - width: 777px; - background: blue URL('../images/header_inner.png') no-repeat; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - margin: 0 auto 0 auto; -} - -a.link, a, a.active { - color: #369; -} - - -#main_content { - color: black; - font-size: 127%; - background-color: white; - width: 757px; - margin: 0 auto 0 auto; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - padding: 10px; -} - -#sidebar { - border: 1px solid #aaa; - background-color: #eee; - margin: 0.5em; - padding: 1em; - float: right; - width: 200px; - font-size: 88%; -} - -#sidebar h2 { - margin-top: 0; -} - -#sidebar ul { - margin-left: 1.5em; - padding-left: 0; -} - -h1,h2,h3,h4,h5,h6,#getting_started_steps { - font-family: "Century Schoolbook L", Georgia, serif; - font-weight: bold; -} - -h2 { - font-size: 150%; -} - -#footer { - border: 1px solid #aaa; - border-top: 0px none; - color: #999; - background-color: white; - padding: 10px; - font-size: 80%; - text-align: center; - width: 757px; - margin: 0 auto 1em auto; -} - -.code { - font-family: monospace; -} - -span.code { - font-weight: bold; - background: #eee; -} - -#status_block { - margin: 0 auto 0.5em auto; - padding: 15px 10px 15px 55px; - background: #cec URL('../images/ok.png') left center no-repeat; - border: 1px solid #9c9; - width: 450px; - font-size: 120%; - font-weight: bolder; -} - -.notice { - margin: 0.5em auto 0.5em auto; - padding: 15px 10px 15px 55px; - width: 450px; - background: #eef URL('../images/info.png') left center no-repeat; - border: 1px solid #cce; -} - -.fielderror { - color: red; - font-weight: bold; -} diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/transparent.gif b/docs/tutorials/wiki2/src/authorization/tutorial/static/transparent.gif Binary files differnew file mode 100644 index 000000000..0341802e5 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/transparent.gif diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt index 05e2ecd76..ca28b9fa5 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt @@ -1,35 +1,62 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki) - Editing: ${page.name}</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <title>${page.name} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/ie6.css')}" + type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> - <body> - -<div class="main_content"> - <div style="float:right; width: 10em;"> Viewing - <span tal:replace="page.name">Page Name Goes Here</span> <br/> - You can return to the <a href="${request.application_url}" - >FrontPage</a>. - <span tal:condition="logged_in"> - <a href="${request.application_url}/logout">Logout</a> - </span> - </div> - - <div> - <form action="${save_url}" method="post"> - <textarea name="body" tal:content="page.data" rows="10" cols="60"/> - <input type="submit" name="form.submitted" value="Save"/> - </form> + <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"> + Editing <b><span tal:replace="page.name">Page Name + Goes Here</span></b><br/> + You can return to the + <a href="${request.application_url}">FrontPage</a>.<br/> + </div> + <div id="right" class="app-welcome align-right"> + <span tal:condition="logged_in"> + <a href="${request.application_url}/logout">Logout</a> + </span> + </div> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <form action="${save_url}" method="post"> + <textarea name="body" tal:content="page.data" rows="10" + cols="60"/><br/> + <input type="submit" name="form.submitted" value="Save"/> + </form> + </div> + </div> + </div> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> </div> -</div> </body> </html> diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt index c56983d64..64e592ea9 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt +++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt @@ -1,32 +1,58 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" - xmlns:tal="http://xml.zope.org/namespaces/tal"> - +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <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> - -<h1>Log In</h1> - -<div tal:replace="message"/> - -<div class="main_content"> - <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 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> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> </html> diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt index 6ad23d44f..d98420680 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt @@ -1,79 +1,75 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.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&subset=latin" type="text/css" media="screen" charset="utf-8" /> - <!--[if !IE 7]> - <style type="text/css"> - #wrap {display:table;height:100%} - </style> - <![endif]--> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> <body> - <div id="wrap"> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <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> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Go" /> + </form> + </div> + <div id="right" class="align-left"> + <h2>Pyramid links</h2> + <ul class="links"> + <li> + <a href="http://pylonsproject.org">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt index 0c654250a..5a69818c1 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt +++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt @@ -1,31 +1,65 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>${page.name} - Pyramid tutorial wiki - (based on TurboGears 20-Minute Wiki)</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <title>${page.name} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/ie6.css')}" + type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> - <body> - -<div class="main_content"> -<div style="float:right; width: 10em;"> Viewing -<span tal:replace="page.name">Page Name Goes Here</span> <br/> -You can return to the <a href="${request.application_url}">FrontPage</a>. -<span tal:condition="logged_in"> - <a href="${request.application_url}/logout">Logout</a> -</span> -</div> - -<div tal:replace="structure content">Page text goes here.</div> -<p><a tal:attributes="href edit_url" href="">Edit this page</a></p> -</div> - + <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"> + Viewing <b><span tal:replace="page.name">Page Name + Goes Here</span></b><br/> + You can return to the + <a href="${request.application_url}">FrontPage</a>.<br/> + </div> + <div id="right" class="app-welcome align-right"> + <span tal:condition="logged_in"> + <a href="${request.application_url}/logout">Logout</a> + </span> + </div> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div tal:replace="structure content"> + Page text goes here. + </div> + <p> + <a tal:attributes="href edit_url" href=""> + Edit this page + </a> + </p> + </div> + </div> + </div> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> </html> diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py index 1020a8b99..332031ba4 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py @@ -1,6 +1,5 @@ import unittest -from pyramid.config import Configurator from pyramid import testing def _initTestingDB(): @@ -20,28 +19,28 @@ def _registerRoutes(config): class ViewWikiTests(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() + testing.tearDown() + + def _callFUT(self, request): + from tutorial.views import view_wiki + return view_wiki(request) def test_it(self): - from tutorial.views import view_wiki - self.config.add_route('view_page', '{pagename}') + _registerRoutes(self.config) request = testing.DummyRequest() - response = view_wiki(request) + response = self._callFUT(request) self.assertEqual(response.location, 'http://example.com/FrontPage') class ViewPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.session.remove() - self.config.end() + testing.tearDown() def _callFUT(self, request): from tutorial.views import view_page @@ -71,12 +70,11 @@ class ViewPageTests(unittest.TestCase): class AddPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): self.session.remove() - self.config.end() + testing.tearDown() def _callFUT(self, request): from tutorial.views import add_page @@ -104,12 +102,11 @@ class AddPageTests(unittest.TestCase): class EditPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): self.session.remove() - self.config.end() + testing.tearDown() def _callFUT(self, request): from tutorial.views import edit_page diff --git a/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in b/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in new file mode 100644 index 000000000..81beba1b1 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/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/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index b0cfd12a7..3b615f635 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -11,9 +11,13 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] pipeline = egg:WebError#evalerror - egg:repoze.tm2#tm + tm tutorial +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini new file mode 100644 index 000000000..0fdc38811 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/production.ini @@ -0,0 +1,77 @@ +[app:tutorial] +use = egg:tutorial +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en +sqlalchemy.url = sqlite:///%(here)s/tutorial.db + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + +[pipeline:main] +pipeline = + weberror + tm + tutorial + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[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][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py index 09764f40f..eaf1ddcfe 100644 --- a/docs/tutorials/wiki2/src/basiclayout/setup.py +++ b/docs/tutorials/wiki2/src/basiclayout/setup.py @@ -11,7 +11,7 @@ requires = [ 'pyramid', 'SQLAlchemy', 'transaction', - 'repoze.tm2', + 'repoze.tm2>=1.0b1', # default_commit_veto 'zope.sqlalchemy', 'WebError', ] diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py index 9da906752..4fd010c5c 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py @@ -12,7 +12,8 @@ from sqlalchemy.orm import sessionmaker from zope.sqlalchemy import ZopeTransactionExtension -DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) +DBSession = scoped_session(sessionmaker( + extension=ZopeTransactionExtension())) Base = declarative_base() class MyModel(Base): diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/footerbg.png b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/footerbg.png Binary files differnew file mode 100644 index 000000000..1fbc873da --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/footerbg.png diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/headerbg.png b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/headerbg.png Binary files differnew file mode 100644 index 000000000..0596f2020 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/headerbg.png diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/ie6.css b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/logo.png b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/middlebg.png b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/middlebg.png Binary files differnew file mode 100644 index 000000000..2369cfb7d --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/middlebg.png diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css index c153be07f..fd1914d8d 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pylons.css @@ -4,34 +4,23 @@ 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;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} @@ -42,23 +31,26 @@ 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;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#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;} @@ -67,7 +59,7 @@ 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=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid-small.png b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid-small.png diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid.png b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid.png Binary files differnew file mode 100644 index 000000000..347e05549 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid.png diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/transparent.gif b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/transparent.gif Binary files differnew file mode 100644 index 000000000..0341802e5 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/transparent.gif diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt index 6ad23d44f..d98420680 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt @@ -1,79 +1,75 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.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&subset=latin" type="text/css" media="screen" charset="utf-8" /> - <!--[if !IE 7]> - <style type="text/css"> - #wrap {display:table;height:100%} - </style> - <![endif]--> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> <body> - <div id="wrap"> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <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> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Go" /> + </form> + </div> + <div id="right" class="align-left"> + <h2>Pyramid links</h2> + <ul class="links"> + <li> + <a href="http://pylonsproject.org">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py index fa3788340..5efa6affa 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py @@ -1,5 +1,4 @@ import unittest -from pyramid.config import Configurator from pyramid import testing def _initTestingDB(): @@ -10,12 +9,11 @@ def _initTestingDB(): class TestMyView(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() _initTestingDB() def tearDown(self): - self.config.end() + testing.tearDown() def test_it(self): from tutorial.views import my_view diff --git a/docs/tutorials/wiki2/src/models/MANIFEST.in b/docs/tutorials/wiki2/src/models/MANIFEST.in new file mode 100644 index 000000000..81beba1b1 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/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/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index b0cfd12a7..3b615f635 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -11,9 +11,13 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] pipeline = egg:WebError#evalerror - egg:repoze.tm2#tm + tm tutorial +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini new file mode 100644 index 000000000..0fdc38811 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -0,0 +1,77 @@ +[app:tutorial] +use = egg:tutorial +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en +sqlalchemy.url = sqlite:///%(here)s/tutorial.db + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + +[pipeline:main] +pipeline = + weberror + tm + tutorial + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[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][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py index 09764f40f..eaf1ddcfe 100644 --- a/docs/tutorials/wiki2/src/models/setup.py +++ b/docs/tutorials/wiki2/src/models/setup.py @@ -11,7 +11,7 @@ requires = [ 'pyramid', 'SQLAlchemy', 'transaction', - 'repoze.tm2', + 'repoze.tm2>=1.0b1', # default_commit_veto 'zope.sqlalchemy', 'WebError', ] diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py index 23b8afab8..797fff929 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models.py @@ -12,7 +12,8 @@ from sqlalchemy.orm import sessionmaker from zope.sqlalchemy import ZopeTransactionExtension -DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) +DBSession = scoped_session(sessionmaker( + extension=ZopeTransactionExtension())) Base = declarative_base() class Page(Base): diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/footerbg.png b/docs/tutorials/wiki2/src/models/tutorial/static/footerbg.png Binary files differnew file mode 100644 index 000000000..1fbc873da --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/static/footerbg.png diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/headerbg.png b/docs/tutorials/wiki2/src/models/tutorial/static/headerbg.png Binary files differnew file mode 100644 index 000000000..0596f2020 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/static/headerbg.png diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/ie6.css b/docs/tutorials/wiki2/src/models/tutorial/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/logo.png b/docs/tutorials/wiki2/src/models/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki2/src/models/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/middlebg.png b/docs/tutorials/wiki2/src/models/tutorial/static/middlebg.png Binary files differnew file mode 100644 index 000000000..2369cfb7d --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/static/middlebg.png diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css index c153be07f..fd1914d8d 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/models/tutorial/static/pylons.css @@ -4,34 +4,23 @@ 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;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} @@ -42,23 +31,26 @@ 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;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#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;} @@ -67,7 +59,7 @@ 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=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/pyramid-small.png b/docs/tutorials/wiki2/src/models/tutorial/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/static/pyramid-small.png diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/pyramid.png b/docs/tutorials/wiki2/src/models/tutorial/static/pyramid.png Binary files differnew file mode 100644 index 000000000..347e05549 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/static/pyramid.png diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/transparent.gif b/docs/tutorials/wiki2/src/models/tutorial/static/transparent.gif Binary files differnew file mode 100644 index 000000000..0341802e5 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/static/transparent.gif diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt index 6ad23d44f..d98420680 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt @@ -1,79 +1,75 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.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&subset=latin" type="text/css" media="screen" charset="utf-8" /> - <!--[if !IE 7]> - <style type="text/css"> - #wrap {display:table;height:100%} - </style> - <![endif]--> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> <body> - <div id="wrap"> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <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> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Go" /> + </form> + </div> + <div id="right" class="align-left"> + <h2>Pyramid links</h2> + <ul class="links"> + <li> + <a href="http://pylonsproject.org">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py index 42b0aaada..71f5e21e3 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py @@ -1,5 +1,4 @@ import unittest -from pyramid.config import Configurator from pyramid import testing def _initTestingDB(): @@ -9,12 +8,11 @@ def _initTestingDB(): class TestMyView(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() _initTestingDB() def tearDown(self): - self.config.end() + testing.tearDown() def test_it(self): from tutorial.views import my_view diff --git a/docs/tutorials/wiki2/src/views/MANIFEST.in b/docs/tutorials/wiki2/src/views/MANIFEST.in new file mode 100644 index 000000000..81beba1b1 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/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/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index b0cfd12a7..3b615f635 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -11,9 +11,13 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.db [pipeline:main] pipeline = egg:WebError#evalerror - egg:repoze.tm2#tm + tm tutorial +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + [server:main] use = egg:Paste#http host = 0.0.0.0 diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini new file mode 100644 index 000000000..0fdc38811 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/production.ini @@ -0,0 +1,77 @@ +[app:tutorial] +use = egg:tutorial +reload_templates = false +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = false +default_locale_name = en +sqlalchemy.url = sqlite:///%(here)s/tutorial.db + +[filter:weberror] +use = egg:WebError#error_catcher +debug = false +;error_log = +;show_exceptions_in_wsgi_errors = true +;smtp_server = localhost +;error_email = janitor@example.com +;smtp_username = janitor +;smtp_password = "janitor's password" +;from_address = paste@localhost +;error_subject_prefix = "Pyramid Error" +;smtp_use_tls = +;error_message = + +[filter:tm] +use = egg:repoze.tm2#tm +commit_veto = repoze.tm:default_commit_veto + +[pipeline:main] +pipeline = + weberror + tm + tutorial + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[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][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py index 58f464f8c..ae9869d50 100644 --- a/docs/tutorials/wiki2/src/views/setup.py +++ b/docs/tutorials/wiki2/src/views/setup.py @@ -11,7 +11,7 @@ requires = [ 'pyramid', 'SQLAlchemy', 'transaction', - 'repoze.tm2', + 'repoze.tm2>=1.0b1', # default_commit_veto 'zope.sqlalchemy', 'WebError', 'docutils', diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py index 334fde814..1a8d24499 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py @@ -10,7 +10,7 @@ def main(global_config, **settings): initialize_sql(engine) config = Configurator(settings=settings) config.add_static_view('static', 'tutorial:static') - config.add_route('home', '/', view='tutorial.views.view_wiki') + config.add_route('view_wiki', '/', view='tutorial.views.view_wiki') config.add_route('view_page', '/{pagename}', view='tutorial.views.view_page', view_renderer='tutorial:templates/view.pt') diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/footerbg.png b/docs/tutorials/wiki2/src/views/tutorial/static/footerbg.png Binary files differnew file mode 100644 index 000000000..1fbc873da --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/static/footerbg.png diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/headerbg.png b/docs/tutorials/wiki2/src/views/tutorial/static/headerbg.png Binary files differnew file mode 100644 index 000000000..0596f2020 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/static/headerbg.png diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/ie6.css b/docs/tutorials/wiki2/src/views/tutorial/static/ie6.css new file mode 100644 index 000000000..b7c8493d8 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/logo.png b/docs/tutorials/wiki2/src/views/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki2/src/views/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/middlebg.png b/docs/tutorials/wiki2/src/views/tutorial/static/middlebg.png Binary files differnew file mode 100644 index 000000000..2369cfb7d --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/static/middlebg.png diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css b/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css index c153be07f..fd1914d8d 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css +++ b/docs/tutorials/wiki2/src/views/tutorial/static/pylons.css @@ -4,34 +4,23 @@ 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;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} @@ -42,23 +31,26 @@ 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;} +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} +#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;} @@ -67,7 +59,7 @@ 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=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/pyramid-small.png b/docs/tutorials/wiki2/src/views/tutorial/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/static/pyramid-small.png diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/pyramid.png b/docs/tutorials/wiki2/src/views/tutorial/static/pyramid.png Binary files differnew file mode 100644 index 000000000..347e05549 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/static/pyramid.png diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/style.css b/docs/tutorials/wiki2/src/views/tutorial/static/style.css deleted file mode 100644 index cad87e0d4..000000000 --- a/docs/tutorials/wiki2/src/views/tutorial/static/style.css +++ /dev/null @@ -1,109 +0,0 @@ -html, body { - color: black; - background-color: #ddd; - font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, sans-serif; - margin: 0; - padding: 0; -} - -td, th {padding:3px;border:none;} -tr th {text-align:left;background-color:#f0f0f0;color:#333;} -tr.odd td {background-color:#edf3fe;} -tr.even td {background-color:#fff;} - -#header { - height: 80px; - width: 777px; - background: blue URL('../images/header_inner.png') no-repeat; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - margin: 0 auto 0 auto; -} - -a.link, a, a.active { - color: #369; -} - - -#main_content { - color: black; - font-size: 127%; - background-color: white; - width: 757px; - margin: 0 auto 0 auto; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - padding: 10px; -} - -#sidebar { - border: 1px solid #aaa; - background-color: #eee; - margin: 0.5em; - padding: 1em; - float: right; - width: 200px; - font-size: 88%; -} - -#sidebar h2 { - margin-top: 0; -} - -#sidebar ul { - margin-left: 1.5em; - padding-left: 0; -} - -h1,h2,h3,h4,h5,h6,#getting_started_steps { - font-family: "Century Schoolbook L", Georgia, serif; - font-weight: bold; -} - -h2 { - font-size: 150%; -} - -#footer { - border: 1px solid #aaa; - border-top: 0px none; - color: #999; - background-color: white; - padding: 10px; - font-size: 80%; - text-align: center; - width: 757px; - margin: 0 auto 1em auto; -} - -.code { - font-family: monospace; -} - -span.code { - font-weight: bold; - background: #eee; -} - -#status_block { - margin: 0 auto 0.5em auto; - padding: 15px 10px 15px 55px; - background: #cec URL('../images/ok.png') left center no-repeat; - border: 1px solid #9c9; - width: 450px; - font-size: 120%; - font-weight: bolder; -} - -.notice { - margin: 0.5em auto 0.5em auto; - padding: 15px 10px 15px 55px; - width: 450px; - background: #eef URL('../images/info.png') left center no-repeat; - border: 1px solid #cce; -} - -.fielderror { - color: red; - font-weight: bold; -} diff --git a/docs/tutorials/wiki2/src/views/tutorial/static/transparent.gif b/docs/tutorials/wiki2/src/views/tutorial/static/transparent.gif Binary files differnew file mode 100644 index 000000000..0341802e5 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/static/transparent.gif diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt index 047a64eb3..3f2039cb6 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt @@ -1,32 +1,58 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki) - Editing: ${page.name}</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <title>${page.name} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/ie6.css')}" + type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> - <body> - -<div class="main_content"> - <div style="float:right; width: 10em;"> Viewing - <span tal:replace="page.name">Page Name Goes Here</span> <br/> - You can return to the <a href="${request.application_url}" - >FrontPage</a>. - </div> - - <div> - <form action="${save_url}" method="post"> - <textarea name="body" tal:content="page.data" rows="10" cols="60"/> - <input type="submit" name="form.submitted" value="Save"/> - </form> + <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"> + Editing <b><span tal:replace="page.name">Page Name Goes + Here</span></b><br/> + You can return to the + <a href="${request.application_url}">FrontPage</a>.<br/> + </div> + <div id="right" class="app-welcome align-right"></div> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <form action="${save_url}" method="post"> + <textarea name="body" tal:content="page.data" rows="10" + cols="60"/><br/> + <input type="submit" name="form.submitted" value="Save"/> + </form> + </div> + </div> + </div> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> </div> -</div> </body> </html> diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt index 6ad23d44f..d98420680 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt @@ -1,79 +1,75 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> <head> - <title>The Pyramid Web Application Development Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.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&subset=latin" type="text/css" media="screen" charset="utf-8" /> - <!--[if !IE 7]> - <style type="text/css"> - #wrap {display:table;height:100%} - </style> - <![endif]--> + <title>The Pyramid Web Application Development Framework</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Neuton|Nobile:regular,i,b,bi&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" href="${request.static_url('tutorial:static/ie6.css')}" type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> <body> - <div id="wrap"> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <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> + <div id="wrap"> + <div id="top"> + <div class="top align-center"> + <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + </div> + </div> + <div id="middle"> + <div class="middle align-center"> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h2>Search documentation</h2> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Go" /> + </form> + </div> + <div id="right" class="align-left"> + <h2>Pyramid links</h2> + <ul class="links"> + <li> + <a href="http://pylonsproject.org">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonsproject.org/projects/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> + </div> + </div> + </div> + <div id="footer"> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> -</html>
\ No newline at end of file +</html> diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt index 86fcc914e..423c1d5a1 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt @@ -1,28 +1,61 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" +<!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> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>${page.name} - Pyramid tutorial wiki - (based on TurboGears 20-Minute Wiki)</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> + <title>${page.name} - Pyramid tutorial wiki (based on + TurboGears 20-Minute Wiki)</title> + <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> + <meta name="keywords" content="python web application" /> + <meta name="description" content="pyramid web application" /> + <link rel="shortcut icon" + href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/pylons.css')}" + type="text/css" media="screen" charset="utf-8" /> + <!--[if lte IE 6]> + <link rel="stylesheet" + href="${request.static_url('tutorial:static/ie6.css')}" + type="text/css" media="screen" charset="utf-8" /> + <![endif]--> </head> - <body> - -<div class="main_content"> -<div style="float:right; width: 10em;"> Viewing -<span tal:replace="page.name">Page Name Goes Here</span> <br/> -You can return to the <a href="${request.application_url}">FrontPage</a>. -</div> - -<div tal:replace="structure content">Page text goes here.</div> -<p><a tal:attributes="href edit_url" href="">Edit this page</a></p> -</div> - + <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"> + Viewing <b><span tal:replace="page.name">Page Name + Goes Here</span></b><br/> + You can return to the + <a href="${request.application_url}">FrontPage</a>.<br/> + </div> + <div id="right" class="app-welcome align-right"></div> + </div> + </div> + <div id="bottom"> + <div class="bottom"> + <div tal:replace="structure content"> + Page text goes here. + </div> + <p> + <a tal:attributes="href edit_url" href=""> + Edit this page + </a> + </p> + </div> + </div> + </div> + <div id="footer"> + <div class="footer" + >© Copyright 2008-2011, Agendaless Consulting.</div> + </div> </body> </html> diff --git a/docs/tutorials/wiki2/src/views/tutorial/tests.py b/docs/tutorials/wiki2/src/views/tutorial/tests.py index 7b770f927..0bc343833 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/views/tutorial/tests.py @@ -1,6 +1,5 @@ import unittest -from pyramid.config import Configurator from pyramid import testing def _initTestingDB(): @@ -20,28 +19,29 @@ def _registerRoutes(config): class ViewWikiTests(unittest.TestCase): def setUp(self): - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): - self.config.end() - - def test_it(self): + testing.tearDown() + + def _callFUT(self, request): from tutorial.views import view_wiki - self.config.add_route('view_page', '{pagename}') + return view_wiki(request) + + def test_it(self): + _registerRoutes(self.config) request = testing.DummyRequest() - response = view_wiki(request) + response = self._callFUT(request) self.assertEqual(response.location, 'http://example.com/FrontPage') class ViewPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): self.session.remove() - self.config.end() + testing.tearDown() def _callFUT(self, request): from tutorial.views import view_page @@ -71,12 +71,12 @@ class ViewPageTests(unittest.TestCase): class AddPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - self.config = Configurator(autocommit=True) + self.config = testing.setUp() self.config.begin() def tearDown(self): self.session.remove() - self.config.end() + testing.tearDown() def _callFUT(self, request): from tutorial.views import add_page @@ -104,12 +104,11 @@ class AddPageTests(unittest.TestCase): class EditPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() - self.config = Configurator(autocommit=True) - self.config.begin() + self.config = testing.setUp() def tearDown(self): self.session.remove() - self.config.end() + testing.tearDown() def _callFUT(self, request): from tutorial.views import edit_page diff --git a/docs/tutorials/zeo/index.rst b/docs/tutorials/zeo/index.rst deleted file mode 100644 index b47c6c477..000000000 --- a/docs/tutorials/zeo/index.rst +++ /dev/null @@ -1,238 +0,0 @@ -.. _zodb_with_zeo: - -Using ZODB with ZEO -=================== - -:term:`ZODB` is a Python object persistence mechanism. :term:`ZODB` -works well as a storage mechanism for :app:`Pyramid` applications, -especially in applications that use :term:`traversal`. - -:term:`ZEO` is an extension to ZODB which allows more than one process -to simultaneously communicate with a ZODB storage. Making a ZODB -database accessible to more than one process means that you can debug -your application objects at the same time that a :app:`Pyramid` -server that accesses the database is running, and will also allow your -application to run under multiprocess configurations, such as those -exposed by :term:`mod_wsgi`. - -The easiest way to get started with ZODB in a :app:`Pyramid` application is -to use the ZODB ``pyramid_zodb`` paster template. See -:ref:`additional_paster_templates` for more information about using this -template. However, the Paster template does not set up a ZEO-capable -application. This chapter shows you how to do that "from scratch". - -Installing Dependencies ------------------------ - -#. Edit your :app:`Pyramid` application's ``setup.py`` file, adding - the following packages to the ``install_requires`` of the - application: - - - ``repoze.folder`` - - - ``repoze.retry`` - - - ``repoze.tm2`` - - - ``repoze.zodbconn`` - - For example, the relevant portion of your application's - ``setup.py`` file might look like so when you're finished adding - the dependencies. - - .. code-block:: python - :linenos: - - setup( - # ... other elements left out for brevity - install_requires=[ - 'pyramid', - 'repoze.folder', - 'repoze.retry', - 'repoze.tm2', - 'repoze.zodbconn', - ], - # ... other elements left out for brevity - ) - -#. Rerun your application's ``setup.py`` file (e.g. using ``python - setup.py develop``) to get these packages installed. A number of - packages will be installed, including ``ZODB``. For the purposes - of this tutorial, we'll assume that your "application" is actually - just the result of the ``pyramid_starter`` Paster template. - -Configuration -------------- - -#. Edit your application's Paste ``development.ini`` file. - - If you already have an ``app`` section in the ``.ini`` file named - ``main``, rename this section to ``myapp`` (e.g. ``app:main`` -> - ``app:myapp``). Add a key to it named ``zodb_uri``, e.g. - - .. code-block:: ini - - [app:myapp] - use = egg:myapp#app - zodb_uri = zeo://%(here)s/zeo.sock - reload_templates = true - debug_authorization = false - debug_notfound = false - - If a ``pipeline`` named ``main`` does not already exist in the - paste ``.ini`` file , add a ``pipeline`` section named ``main``. - Put the names ``connector``, ``egg:repoze.retry#retry``, and - ``egg:repoze.tm2#tm`` to the top of the pipeline. - - .. code-block:: ini - - [pipeline:main] - pipeline = - egg:repoze.retry#retry - egg:repoze.tm2#tm - myapp - - When you're finished, your ``.ini`` file might look like so: - - .. code-block:: ini - - [DEFAULT] - debug = true - - [app:myapp] - use = egg:myapp#app - zodb_uri = zeo://%(here)s/zeo.sock - reload_templates = true - debug_authorization = false - debug_notfound = false - - [pipeline:main] - pipeline = - egg:repoze.retry#retry - egg:repoze.tm2#tm - myapp - - [server:main] - use = egg:Paste#http - host = 0.0.0.0 - port = 6543 - - See :ref:`MyProject_ini` for more information about project Paste - ``.ini`` files. - -#. Add a ``zeo.conf`` file to your package with the following - contents: - - .. code-block:: text - - %define INSTANCE . - - <zeo> - address $INSTANCE/zeo.sock - read-only false - invalidation-queue-size 100 - pid-filename $INSTANCE/zeo.pid - </zeo> - - <blobstorage 1> - <filestorage> - path $INSTANCE/myapp.db - </filestorage> - blob-dir $INSTANCE/blobs - </blobstorage> - -#. For the purposes of this tutorial we'll assume that you want your - :app:`Pyramid` application's :term:`root` object to be a - "folderish" object. To achieve this, change your application's - ``models.py`` file to look like the below: - - .. code-block:: python - - from repoze.folder import Folder - - class MyModel(Folder): - pass - - def appmaker(root): - if not 'myapp' in root: - root['myapp'] = MyModel() - transaction.commit() - return root['myapp'] - -#. Change your application's ``__init__.py`` to look something like the - below: - - .. code-block:: python - - from pyramid.config import Configurator - from repoze.zodbconn.finder import PersistentApplicationFinder - from myapp.models import appmaker - import transaction - - def app(global_config, **settings): - """ This function returns a ``pyramid`` WSGI - application. - - It is usually called by the PasteDeploy framework during - ``paster serve``""" - # paster app config callback - zodb_uri = settings['zodb_uri'] - finder = PersistentApplicationFinder(zodb_uri, appmaker) - def get_root(request): - return finder(request.environ) - config = Configurator(root_factory=get_root, settings=settings) - # .. other configuration statements .. - return config.make_wsgi_app() - -Running -------- - -#. Start the ZEO server in a terminal with the current directory set - to the package directory: - - .. code-block:: text - - ../bin/runzeo -C zeo.conf - - You should see something like this, as a result: - - .. code-block:: text - :linenos: - - [chrism@snowpro myapp]$ ../bin/runzeo -C zeo.conf - ------ - 2009-09-19T13:48:41 INFO ZEO.runzeo (9910) created PID file './zeo.pid' - # ... more output ... - 2009-09-19T13:48:41 INFO ZEO.zrpc (9910) listening on ./zeo.sock - -#. While the ZEO server is running, start the application server: - - .. code-block:: text - :linenos: - - [chrism@snowpro myapp]$ ../bin/paster serve myapp.ini - Starting server in PID 10177. - serving on 0.0.0.0:6543 view at http://127.0.0.1:6543 - -#. The root object is now a "folderish" ZODB object. Nothing else - about the application has changed. - -#. You can manipulate the database directly (even when the - application's HTTP server is running) by using the ``pshell`` - command in a third terminal window: - - .. code-block:: text - :linenos: - - [chrism@snowpro sess]$ ../bin/paster --plugin=pyramid pshell \ - myapp.ini myapp - Python 2.5.4 (r254:67916, Sep 4 2009, 02:12:16) - [GCC 4.2.1 (Apple Inc. build 5646)] on darwin - Type "help" for more information. "root" is the Pyramid app root object. - >>> root - <sess.models.MyModel object None at 0x16438f0> - >>> root.foo = 'bar' - >>> import transaction - >>> transaction.commit() - - diff --git a/docs/whatsnew-1.0.rst b/docs/whatsnew-1.0.rst new file mode 100644 index 000000000..4a41840eb --- /dev/null +++ b/docs/whatsnew-1.0.rst @@ -0,0 +1,577 @@ +What's New In Pyramid 1.0 +========================= + +This article explains the new features in Pyramid version 1.0 as compared to +its predecessor, :mod:`repoze.bfg` 1.3. It also documents backwards +incompatibilities between the two versions and deprecations added to Pyramid +1.0, as well as software dependency changes and notable documentation +additions. + +Major Feature Additions +----------------------- + +The major feature additions in Pyramid 1.0 are: + +- New name and branding association with the Pylons Project. + +- BFG conversion script + +- Paster template improvements + +- Terminology changes + +- Better platform compatibility and support + +- Direct built-in support for the Mako templating language. + +- Built-in support for sessions. + +- Updated URL dispatch features + +- Better imperative extensibility + +- ZCML externalized + +- Better support for global template variables during rendering + +- View mappers + +- Testing system improvements + +- Authentication support improvements + +- Documentation improvements + +New Name and Branding +~~~~~~~~~~~~~~~~~~~~~ + +The name of ``repoze.bfg`` has been changed to Pyramid. The project is also +now a subproject of a new entity, "The Pylons Project". The Pylons Project +is the project name for a collection of web-framework-related technologies. +Pyramid was the first package in the Pylons Project. Other packages to the +collection have been added over time, such as support packages useful for +Pylons 1 users as well as ex-Zope users. Pyramid is the successor to both +:mod:`repoze.bfg` and :term:`Pylons` version 1. + +The Pyramid codebase is derived almost entirely from :mod:`repoze.bfg` +with some changes made for the sake of Pylons 1 compatibility. + +Pyramid is technically backwards incompatible with :mod:`repoze.bfg`, as it +has a new package name, so older imports from the ``repoze.bfg`` module will +fail if you do nothing to your existing :mod:`repoze.bfg` application. +However, you won't have to do much to use your existing BFG applications on +Pyramid. There's automation which will change most of your import statements +and ZCML declarations. See +http://docs.pylonshq.com/pyramid/dev/tutorials/bfg/index.html for upgrade +instructions. + +Pylons 1 users will need to do more work to use Pyramid, as Pyramid shares no +"DNA" with Pylons. It is hoped that over time documentation and upgrade code +will be developed to help Pylons 1 users transition to Pyramid more easily. + +:mod:`repoze.bfg` version 1.3 will be its last major release. Minor updates +will be made for critical bug fixes. Pylons version 1 will continue to see +maintenance releases, as well. + +The Repoze project will continue to exist. Repoze will be able to regain its +original focus: bringing Zope technologies to WSGI. The popularity of +:mod:`repoze.bfg` as its own web framework hindered this goal. + +We hope that people are attracted at first by the spirit of cooperation +demonstrated by the Pylons Project and the merging of development +communities. It takes humility to sacrifice a little sovereignty and work +together. The opposite, forking or splintering of projects, is much more +common in the open source world. We feel there is a limited amount of oxygen +in the space of "top-tier" Python web frameworks and we don’t do the Python +community a service by over-crowding. By merging the :mod:`repoze.bfg` and +the philosophically-similar Pylons communities, both gain an expanded +audience and a stronger chance of future success. + +BFG Conversion Script +~~~~~~~~~~~~~~~~~~~~~ + +The ``bfg2pyramid`` conversion script performs a mostly automated conversion +of an existing :mod:`repoze.bfg` application to Pyramid. The process is +described in :ref:`converting_a_bfg_app`. + +Paster Template Improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- The paster templates now have much nicer CSS and graphics. + +- The ``development.ini`` generated by all paster templates is now configured + to use the :term:`WebError` interactive exception debugger by default. + +- All paster templates have been normalized: each now uses the name ``main`` + to represent the function that returns a WSGI application, and each now has + roughly the same shape of development.ini style. + +- All preexisting paster templates now use "imperative" configuration + (``starter``, ``routesalchemy``, ``alchemy``, ``zodb``) instead of ZCML + configuration. + +- The ``pyramid_zodb``, ``pyramid_routesalchemy`` and ``pyramid_alchemy`` + paster templates now use a default "commit veto" hook when configuring the + ``repoze.tm2`` transaction manager in ``development.ini``. This prevents a + transaction from being committed when the response status code is within + the 400 or 500 ranges. See also + http://docs.repoze.org/tm2/#using-a-commit-veto. + +Terminology Changes +~~~~~~~~~~~~~~~~~~~ + +- The Pyramid concept previously known as "model" is now known as "resource". + As a result, the following API renames have been made. Backwards + compatibility shims for the old names have been left in place in all cases:: + + pyramid.url.model_url -> + pyramid.url.resource_url + + pyramid.traversal.find_model -> + pyramid.url.find_resource + + pyramid.traversal.model_path -> + pyramid.traversal.resource_path + + pyramid.traversal.model_path_tuple -> + pyramid.traversal.resource_path_tuple + + pyramid.traversal.ModelGraphTraverser -> + pyramid.traversal.ResourceTreeTraverser + + pyramid.config.Configurator.testing_models -> + pyramid.config.Configurator.testing_resources + + pyramid.testing.registerModels -> + pyramid.testing.registerResources + + pyramid.testing.DummyModel -> + pyramid.testing.DummyResource + +- All documentation which previously referred to "model" now refers to + "resource". + +- The ``starter`` paster template now has a ``resources.py`` module instead + of a ``models.py`` module. + +- Positional argument names of various APIs have been changed from + ``model`` to ``resource``. + +- The Pyramid concept previously known as "resource" is now known as "asset". + As a result, the following API changes were made. Backwards compatibility + shims have been left in place as necessary:: + + pyramid.config.Configurator.absolute_resource_spec -> + pyramid.config.Configurator.absolute_asset_spec + + pyramid.config.Configurator.override_resource -> + pyramid.config.Configurator.override_asset + + +- The (non-API) module previously known as ``pyramid.resource`` is now + known as ``pyramid.asset``. + +- All docs that previously referred to "resource specification" now refer + to "asset specification". + +- The setting previously known as ``BFG_RELOAD_RESOURCES`` (envvar) or + ``reload_resources`` (config file) is now known, respectively, as + ``PYRAMID_RELOAD_ASSETS`` and ``reload_assets``. + +Better Platform Compatibility and Support +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We've made Pyramid's test suite pass on both Jython and PyPy. However, +Chameleon doesn't work on either, so you'll need to use Mako or Jinja2 +templates on these platforms. + +Sessions +~~~~~~~~ + +Pyramid now has built-in sessioning support, documented in +:ref:`sessions_chapter`. The sessioning implementation is pluggable. It +also provides flash messaging and cross-site-scripting prevention features. + +Using ``request.session`` now returns a (dictionary-like) session object if +a :term:`session factory` has been configured. + +A new argument to the Configurator constructor exists: ``session_factory`` +and a new method on the configurator exists: +:meth:`pyramid.config.Configurator.set_session_factory`. + +Mako +~~~~ + +In addition to Chameleon templating, Pyramid now also provides built-in +support for :term:`Mako` templating. See :ref:`mako_templates` for more +information. + +URL Dispatch +~~~~~~~~~~~~ + +- URL Dispatch now allows for replacement markers to be located anywhere + in the pattern, instead of immediately following a ``/``. + +- URL Dispatch now uses the form ``{marker}`` to denote a replace marker in + the route pattern instead of ``:marker``. The old colon-style marker syntax + is still accepted for backwards compatibility. The new format allows a + regular expression for that marker location to be used instead of the + default ``[^/]+``, for example ``{marker:\d+}`` is now valid to require the + marker to be digits. + +- Addded a new API :func:`pyramid.url.current_route_url`, which computes a + URL based on the "current" route (if any) and its matchdict values. + +- Added a ``paster proute`` command which displays a summary of the routing + table. See the narrative documentation section entitled + :ref:`displaying_application_routes`. + +- Added ``debug_routematch`` configuration setting (settable in your ``.ini`` + file) that logs matched routes including the matchdict and predicates. + +- Add a :func:`pyramid.url.route_path` API, allowing folks to generate + relative URLs. Calling ``route_path`` is the same as calling + :func:`pyramid.url.route_url` with the argument ``_app_url`` equal to the + empty string. + +- Add a :meth:`pyramid.request.Request.route_path` API. This is a + convenience method of the request which calls + :func:`pyramid.url.route_url`. + +- Added class vars ``matchdict`` and ``matched_route`` to + :class:`pyramid.request.Request`. Each is set to ``None`` when a route + isn't matched during a request. + +ZCML Externalized +~~~~~~~~~~~~~~~~~ + +- The ``load_zcml`` method of a Configurator has been removed from the + Pyramid core. Loading ZCML is now a feature of the :term:`pyramid_zcml` + package, which can be downloaded from PyPI. Documentation for the package + should be available via + http://pylonsproject.org/projects/pyramid_zcml/dev/, which describes how to + add a configuration statement to your ``main`` block to reobtain this + method. You will also need to add an ``install_requires`` dependency upon + the ``pyramid_zcml`` distribution to your ``setup.py`` file. + +- The "Declarative Configuration" narrative chapter has been removed (it was + moved to the ``pyramid_zcml`` package). + +- Most references to ZCML in narrative chapters have been removed or + redirected to ``pyramid_zcml`` locations. + +- The ``starter_zcml`` paster template has been moved to the ``pyramid_zcml`` + package. + +Imperative Two-Phase Configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To support application extensibility, the :app:`Pyramid` +:term:`Configurator`, by default, now detects configuration conflicts and +allows you to include configuration imperatively from other packages or +modules. It also, by default, performs configuration in two separate phases. +This allows you to ignore relative configuration statement ordering in some +circumstances. See :ref:`advconfig_narr` for more information. + +The :meth:`pyramid.config.Configurator.add_directive` allows framework +extenders to add methods to the configurator, which allows extenders to avoid +subclassing a Configurator just to add methods. See :ref:`add_directive` for +more info. + +Surrounding application configuration with ``config.begin()`` and +``config.end()`` is no longer necessary. All paster templates have been +changed to no longer call these functions. + +Better Support for Global Template Variables During Rendering +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A new event type named :class:`pyramid.interfaces.IBeforeRender` is now sent +as an event before a renderer is invoked. Applications may now subscribe to +the ``IBeforeRender`` event type in order to introspect the and modify the +set of renderer globals before they are passed to a renderer. The event +object iself has a dictionary-like interface that can be used for this +purpose. For example:: + + from repoze.events import subscriber + from pyramid.interfaces import IRendererGlobalsEvent + + @subscriber(IRendererGlobalsEvent) + def add_global(event): + event['mykey'] = 'foo' + +View Mappers +~~~~~~~~~~~~ + +A "view mapper" subsystem has been extracted, which allows framework +extenders to control how view callables are constructed and called. This +feature is not useful for "civilians", only for extension writers. See +:ref:`using_a_view_mapper` for more information. + +Testing Support Improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :func:`pyramid.testing.setUp` and :func:`pyramid.testing.tearDown` APIs +have been undeprecated. They are now the canonical setup and teardown APIs +for test configuration, replacing "direct" creation of a Configurator. This +is a change designed to provide a facade that will protect against any future +Configurator deprecations. + +Authentication Support Improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- The :class:`pyramid.interfaces.IAuthenticationPolicy` interface now + specifies an ``unauthenticated_userid`` method. This method supports an + important optimization required by people who are using persistent storages + which do not support object caching and whom want to create a "user object" + as a request attribute. + +- A new API has been added to the :mod:`pyramid.security` module named + ``unauthenticated_userid``. This API function calls the + ``unauthenticated_userid`` method of the effective security policy. + +- The class :class:`pyramid.authentication.AuthTktCookieHelper` is now an + API. This class can be used by third-party authentication policy + developers to help in the mechanics of authentication cookie-setting. + +- The :class:`pyramid.authentication.AuthTktAuthenticationPolicy` now accepts + a ``tokens`` parameter via :func:`pyramid.security.remember`. The value + must be a sequence of strings. Tokens are placed into the auth_tkt + "tokens" field and returned in the auth_tkt cookie. + +- Added a ``wild_domain`` argument to + :class:`pyramid.authentication.AuthTktAuthenticationPolicy`, which defaults + to ``True``. If it is set to ``False``, the feature of the policy which + sets a cookie with a wilcard domain will be turned off. + +Documentation Improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Casey Duncan, a good friend, and an excellent technical writer has given us + the gift of professionally editing the entire Pyramid documentation set. + Any faults in the documentation are the development team's, and all + improvements are his. + +- The "Resource Location and View Lookup" chapter has been replaced with a + variant of Rob Miller's "Much Ado About Traversal" (originally published at + http://blog.nonsequitarian.org/2010/much-ado-about-traversal/). + +- Many users have contributed documentation fixes and improvements including + Ben Bangert, Blaise Laflamme, Rob Miller, Mike Orr, Carlos de la Guardia, + Paul Everitt, Tres Seaver, John Shipman, Marius Gedminas, Chris Rossi, + Joachim Krebs, Xavier Spriet, Reed O'Brien, William Chambers, Charlie + Choiniere, and Jamaludin Ahmad. + +Minor Feature Additions +----------------------- + +- The ``settings`` dictionary passed to the Configurator is now available as + ``config.registry.settings`` in configuration code and + ``request.registry.settings`` in view code). + +- :meth:`pyramid.config.Configurator.add_view` now accepts a ``decorator`` + keyword argument, a callable which will decorate the view callable before + it is added to the registry. + +- Allow static renderer provided during view registration to be overridden at + request time via a request attribute named ``override_renderer``, which + should be the name of a previously registered renderer. Useful to provide + "omnipresent" RPC using existing rendered views. + +- If a resource implements a ``__resource_url__`` method, it will be called + as the result of invoking the :func:`pyramid.url.resource_url` function to + generate a URL, overriding the default logic. See + :ref:`generating_the_url_of_a_resource` for more information. + +- The name ``registry`` is now available in a ``pshell`` environment by + default. It is the application registry object. + +- Added support for json on Google App Engine by catching + :exc:`NotImplementedError` and importing ``simplejson`` from + ``django.utils``. + +- Added the :mod:`pyramid.httpexceptions` module, which is a facade for the + ``webob.exc`` module. + +- New class: :class:`pyramid.response.Response`. This is a pure facade for + ``webob.Response`` (old code need not change to use this facade, it's + existence is mostly for vanity and documentation-generation purposes). + +- The request now has a new attribute: ``tmpl_context`` for benefit of + Pylons users. + +- New API methods for :class:`pyramid.request.Request`: ``model_url``, + ``route_url``, and ``static_url``. These are simple passthroughs for their + respective functions in :mod:`pyramid.url`. + +Backwards Incompatibilities +--------------------------- + +- When a :class:`pyramid.exceptions.Forbidden` error is raised, its status + code now ``403 Forbidden``. It was previously ``401 Unauthorized``, for + backwards compatibility purposes with :mod:`repoze.bfg`. This change will + cause problems for users of Pyramid with :mod:`repoze.who`, which + intercepts ``401 Unauthorized`` by default, but allows ``403 Forbidden`` to + pass through. Those deployments will need to configure :mod:`repoze.who` + to also react to ``403 Forbidden``. To do so, use a repoze.who + ``challenge_decider`` that looks like this:: + + import zope.interface + from repoze.who.interfaces import IChallengeDecider + + def challenge_decider(environ, status, headers): + return status.startswith('403') or status.startswith('401') + zope.interface.directlyProvides(challenge_decider, IChallengeDecider) + +- The ``paster bfgshell`` command is now known as ``paster pshell``. + +- There is no longer an ``IDebugLogger`` object registered as a named utility + with the name ``repoze.bfg.debug``. + +- These deprecated APIs have been removed: + ``pyramid.testing.registerViewPermission``, + ``pyramid.testing.registerRoutesMapper``, ``pyramid.request.get_request``, + ``pyramid.security.Unauthorized``, + ``pyramid.view.view_execution_permitted``, ``pyramid.view.NotFound`` + +- The Venusian "category" for all built-in Venusian decorators + (e.g. ``subscriber`` and ``view_config``/``bfg_view``) is now + ``pyramid`` instead of ``bfg``. + +- The ``pyramid.renderers.rendered_response`` function removed; use + :func:`pyramid.renderers.render_to_response` instead. + +- Renderer factories now accept a *renderer info object* rather than an + absolute resource specification or an absolute path. The object has the + following attributes: ``name`` (the ``renderer=`` value), ``package`` (the + 'current package' when the renderer configuration statement was found), + ``type``: the renderer type, ``registry``: the current registry, and + ``settings``: the deployment settings dictionary. Third-party + ``repoze.bfg`` renderer implementations that must be ported to Pyramid will + need to account for this. This change was made primarily to support more + flexible Mako template rendering. + +- The presence of the key ``repoze.bfg.message`` in the WSGI environment when + an exception occurs is now deprecated. Instead, code which relies on this + environ value should use the ``exception`` attribute of the request + (e.g. ``request.exception[0]``) to retrieve the message. + +- The values ``bfg_localizer`` and ``bfg_locale_name`` kept on the request + during internationalization for caching purposes were never APIs. These + however have changed to ``localizer`` and ``locale_name``, respectively. + +- The default ``cookie_name`` value of the + :class:`pyramid.authentication.AuthTktAuthenticationPolicy` now defaults to + ``auth_tkt`` (it used to default to ``repoze.bfg.auth_tkt``). + +- The :func:`pyramid.testing.zcml_configure` API has been removed. It had + been advertised as removed since :mod:`repoze.bfg` 1.2a1, but hadn't + actually been. + +- All environment variables which used to be prefixed with ``BFG_`` are now + prefixed with ``PYRAMID_`` (e.g. ``BFG_DEBUG_NOTFOUND`` is now + ``PYRAMID_DEBUG_NOTFOUND``) + +- Since the :class:`pyramid.interfaces.IAuthenticationPolicy` interface now + specifies that a policy implementation must implement an + ``unauthenticated_userid`` method, all third-party custom authentication + policies now must implement this method. It, however, will only be called + when the global function named + :func:`pyramid.security.unauthenticated_userid` is invoked, so if you're + not invoking that, you will not notice any issues. + +- The ``configure_zcml`` setting within the deployment settings (within + ``**settings`` passed to a Pyramid ``main`` function) has ceased to have any + meaning. + +- The ``make_app`` function has been removed from the :mod:`pyramid.router` + module. It continues life within the ``pyramid_zcml`` package. This + leaves the :mod:`pyramid.router` module without any API functions. + +Deprecations and Behavior Differences +------------------------------------- + +- :class:`pyramid.configuration.Configurator` is now deprecated. Use + :class:`pyramid.config.Configurator`, passing its constructor + ``autocommit=True`` instead. The + :class:`pyramid.configuration.Configurator` alias will live for a long + time, as every application uses it, but its import now issues a deprecation + warning. The :class:`pyramid.config.Configurator` class has the same API + as the :class:`pyramid.configuration.Configurator` class, which it means to + replace, except by default it is a *non-autocommitting* configurator. The + now-deprecated ``pyramid.configuration.Configurator`` will autocommit every + time a configuration method is called. The :mod:`pyramid.configuration` + module remains, but it is deprecated. Use :mod:`pyramid.config` instead. + +- The :func:`pyramid.settings.get_settings` API is now deprecated. Use + ``pyramid.threadlocals.get_current_registry().settings`` instead or use the + ``settings`` attribute of the registry available from the request + (``request.registry.settings``). + +- The decorator previously known as ``pyramid.view.bfg_view`` is now known + most formally as :class:`pyramid.view.view_config` in docs and paster + templates. + +- Obtaining the ``settings`` object via + ``registry.{get|query}Utility(ISettings)`` is now deprecated. Instead, + obtain the ``settings`` object via the ``registry.settings`` attribute. A + backwards compatibility shim was added to the registry object to register + the settings object as an ISettings utility when ``setattr(registry, + 'settings', foo)`` is called, but it will be removed in a later release. + +- Obtaining the ``settings`` object via :func:`pyramid.settings.get_settings` + is now deprecated. Obtain it instead as the ``settings`` attribute of the + registry now (obtain the registry via + :func:`pyramid.threadlocal.get_registry` or as ``request.registry``). + +Dependency Changes +------------------ + +- Depend on Venusian >= 0.5 (for scanning conflict exception decoration). + +Documentation Enhancements +-------------------------- + +- Added a :mod:`pyramid.httpexceptions` API documentation chapter. + +- Added a :mod:`pyramid.session` API documentation chapter. + +- Added an API chapter for the :mod:`pyramid.response` module. + +- Added a :ref:`sessions_chapter` narrative documentation chapter. + +- All documentation which previously referred to ``webob.Response`` now uses + :class:`pyramid.response.Response` instead. + +- The documentation has been overhauled to use imperative configuration, + moving declarative configuration (ZCML) explanations to an external + package, :term:`pyramid_zcml`. + +- Removed ``zodbsessions`` tutorial chapter. It's still useful, but we now + have a SessionFactory abstraction which competes with it, and maintaining + documentation on both ways to do it is a distraction. + +- Added an example of ``WebTest`` functional testing to the testing narrative + chapter at :ref:`functional_tests`. + +- Extended the Resources chapter with examples of calls to resource-related + APIs. + +- Add "Pyramid Provides More Than One Way to Do It" to Design Defense + documentation. + +- The (weak) "Converting a CMF Application to Pyramid" tutorial has been + removed from the tutorials section. It was moved to the + ``pyramid_tutorials`` Github repository at + http://docs.pylonsproject.org/projects/pyramid_tutorials/dev/. + +- Moved "Using ZODB With ZEO" and "Using repoze.catalog Within Pyramid" + tutorials out of core documentation and into the Pyramid Tutorials site + (http://docs.pylonsproject.org/projects/pyramid_tutorials/dev/). + +- Removed API documentation for deprecated ``pyramid.testing`` APIs named + ``registerDummySecurityPolicy``, ``registerResources``, ``registerModels``, + ``registerEventListener``, ``registerTemplateRenderer``, + ``registerDummyRenderer``, ``registerView``, ``registerUtility``, + ``registerAdapter``, ``registerSubscriber``, ``registerRoute``, and + ``registerSettings``. + diff --git a/docs/zcml.rst b/docs/zcml.rst deleted file mode 100644 index e5bbe5d4b..000000000 --- a/docs/zcml.rst +++ /dev/null @@ -1,33 +0,0 @@ -.. _zcml_directives: - -ZCML Directives -=============== - -Comprehensive reference material for every ZCML directive provided by -:app:`Pyramid` is available within this chapter. The ZCML directive -documentation is organized alphabetically by directive name. - -.. toctree:: - :maxdepth: 1 - - zcml/aclauthorizationpolicy - zcml/adapter - zcml/authtktauthenticationpolicy - zcml/asset - zcml/configure - zcml/default_permission - zcml/forbidden - zcml/handler - zcml/include - zcml/localenegotiator - zcml/notfound - zcml/remoteuserauthenticationpolicy - zcml/renderer - zcml/repozewho1authenticationpolicy - zcml/route - zcml/scan - zcml/static - zcml/subscriber - zcml/translationdir - zcml/utility - zcml/view diff --git a/docs/zcml/aclauthorizationpolicy.rst b/docs/zcml/aclauthorizationpolicy.rst deleted file mode 100644 index f09531415..000000000 --- a/docs/zcml/aclauthorizationpolicy.rst +++ /dev/null @@ -1,35 +0,0 @@ -.. _aclauthorizationpolicy_directive: - -``aclauthorizationpolicy`` --------------------------- - -When this directive is used, authorization information is obtained -from :term:`ACL` objects attached to :term:`resource` objects. - -Attributes -~~~~~~~~~~ - -None. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <aclauthorizationpolicy/> - -Alternatives -~~~~~~~~~~~~ - -You may create an instance of the -:class:`pyramid.authorization.ACLAuthorizationPolicy` and pass it -to the :class:`pyramid.config.Configurator` constructor as -the ``authorization_policy`` argument during initial application -configuration. - -See Also -~~~~~~~~ - -See also :ref:`authorization_policies_directives_section` and -:ref:`security_chapter`. diff --git a/docs/zcml/adapter.rst b/docs/zcml/adapter.rst deleted file mode 100644 index 83cce0c39..000000000 --- a/docs/zcml/adapter.rst +++ /dev/null @@ -1,47 +0,0 @@ -.. _adapter_directive: - -``adapter`` ------------ - -Register a :term:`Zope Component Architecture` "adapter". - -Attributes -~~~~~~~~~~ - -``factory`` - The adapter factory (often a class). - -``provides`` - The :term:`interface` that an adapter instance resulting from a - lookup will provide. - -``for`` - Interfaces or classes to be adapted, separated by spaces, - e.g. ``interfaces.IFoo interfaces.IBar``. - -``name`` - The adapter name. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <adapter - for=".foo.IFoo .bar.IBar" - provides=".interfaces.IMyAdapter" - factory=".adapters.MyAdapter" - /> - -Alternatives -~~~~~~~~~~~~ - -Use the ``registerAdapter`` method of the ``registry`` attribute of a -:term:`Configurator` instance during initial application setup. - -See Also -~~~~~~~~ - -None. - diff --git a/docs/zcml/asset.rst b/docs/zcml/asset.rst deleted file mode 100644 index af7a6db94..000000000 --- a/docs/zcml/asset.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. _asset_directive: - -``asset`` ---------- - -The ``asset`` directive adds an asset override for a single -static file/directory asset. - -Attributes -~~~~~~~~~~ - -``to_override`` - A :term:`asset specification` specifying the asset to be - overridden. - -``override_with`` - A :term:`asset specification` specifying the asset which - is used as the override. - -Examples -~~~~~~~~ - -.. topic:: Overriding a Single Asset File - - .. code-block:: xml - :linenos: - - <asset - to_override="some.package:templates/mytemplate.pt" - override_with="another.package:othertemplates/anothertemplate.pt" - /> - -.. topic:: Overriding all Assets in a Package - - .. code-block:: xml - :linenos: - - <asset - to_override="some.package" - override_with="another.package" - /> - -.. topic:: Overriding all Assets in a Subdirectory of a Package - - .. code-block:: xml - :linenos: - - <asset - to_override="some.package:templates/" - override_with="another.package:othertemplates/" - /> - -Alternatives -~~~~~~~~~~~~ - -The :meth:`pyramid.config.Configurator.override_asset` -method can be used instead of the ``resource`` ZCML directive. - -This directive can also be invoked as the ``resource`` ZCML directive for -backwards compatibility purposes. - -See Also -~~~~~~~~ - -See also :ref:`asset_zcml_directive`. diff --git a/docs/zcml/authtktauthenticationpolicy.rst b/docs/zcml/authtktauthenticationpolicy.rst deleted file mode 100644 index 25be4186c..000000000 --- a/docs/zcml/authtktauthenticationpolicy.rst +++ /dev/null @@ -1,102 +0,0 @@ -.. _authtktauthenticationpolicy_directive: - -``authtktauthenticationpolicy`` -------------------------------- - -When this directive is used, authentication information is obtained -from an :mod:`paste.auth.auth_tkt` cookie value, assumed to be set by -a custom login form. - -Attributes -~~~~~~~~~~ - -``secret`` - The ``secret`` is a string that will be used to sign the data - stored by the cookie. It is required and has no default. - -``callback`` - The ``callback`` is a Python dotted name to a function passed the - string representing the userid stored in the cookie and the - request as positional arguments. The callback is expected to - return None if the user represented by the string doesn't exist or - a sequence of group identifiers (possibly empty) if the user does - exist. If ``callback`` is None, the userid will be assumed to - exist with no groups. It defaults to ``None``. - -``cookie_name`` - The ``cookie_name`` is the name used for the cookie that contains - the user information. It defaults to ``auth_tkt``. - -``secure`` - ``secure`` is a boolean value. If it's set to "true", the cookie - will only be sent back by the browser over a secure (HTTPS) - connection. It defaults to "false". - -``include_ip`` - ``include_ip`` is a boolean value. If it's set to true, the - requesting IP address is made part of the authentication data in - the cookie; if the IP encoded in the cookie differs from the IP of - the requesting user agent, the cookie is considered invalid. It - defaults to "false". - -``timeout`` - ``timeout`` is an integer value. It represents the maximum age in - seconds which the auth_tkt ticket will be considered valid. If - ``timeout`` is specified, and ``reissue_time`` is also specified, - ``reissue_time`` must be a smaller value than ``timeout``. It - defaults to ``None``, meaning that the ticket will be considered - valid forever. - -``reissue_time`` - ``reissue_time`` is an integer value. If ``reissue_time`` is - specified, when we encounter a cookie that is older than the - reissue time (in seconds), but younger that the ``timeout``, a new - cookie will be issued. It defaults to ``None``, meaning that - authentication cookies are never reissued. A value of ``0`` means - reissue a cookie in the response to every request that requires - authentication. - -``max_age`` - ``max_age`` is the maximum age of the auth_tkt *cookie*, in - seconds. This differs from ``timeout`` inasmuch as ``timeout`` - represents the lifetime of the ticket contained in the cookie, - while this value represents the lifetime of the cookie itself. - When this value is set, the cookie's ``Max-Age`` and ``Expires`` - settings will be set, allowing the auth_tkt cookie to last between - browser sessions. It is typically nonsensical to set this to a - value that is lower than ``timeout`` or ``reissue_time``, although - it is not explicitly prevented. It defaults to ``None``, meaning - (on all major browser platforms) that auth_tkt cookies will last - for the lifetime of the user's browser session. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <authtktauthenticationpolicy - secret="goshiamsosecret" - callback=".somemodule.somefunc" - cookie_name="mycookiename" - secure="false" - include_ip="false" - timeout="86400" - reissue_time="600" - max_age="31536000" - /> - -Alternatives -~~~~~~~~~~~~ - -You may create an instance of the -:class:`pyramid.authentication.AuthTktAuthenticationPolicy` and -pass it to the :class:`pyramid.config.Configurator` -constructor as the ``authentication_policy`` argument during initial -application configuration. - -See Also -~~~~~~~~ - -See also :ref:`authentication_policies_directives_section` and -:class:`pyramid.authentication.AuthTktAuthenticationPolicy`. diff --git a/docs/zcml/configure.rst b/docs/zcml/configure.rst deleted file mode 100644 index cab8ec204..000000000 --- a/docs/zcml/configure.rst +++ /dev/null @@ -1,95 +0,0 @@ -.. _configure_directive: - -``configure`` -------------- - -Because :term:`ZCML` is XML, and because XML requires a single root -tag for each document, every ZCML file used by :app:`Pyramid` must -contain a ``configure`` container directive, which acts as the root -XML tag. It is a "container" directive because its only job is to -contain other directives. - -Attributes -~~~~~~~~~~ - -``xmlns`` - The default XML namespace used for subdirectives. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <configure xmlns="http://pylonshq.com/pyramid"> - - <!-- other directives --> - - </configure> - -.. _word_on_xml_namespaces: - -A Word On XML Namespaces -~~~~~~~~~~~~~~~~~~~~~~~~ - -Usually, the start tag of the ``<configure>`` container tag has a -default *XML namespace* associated with it. This is usually -``http://pylonshq.com/pyramid``, named by the ``xmlns`` attribute of -the ``configure`` start tag. - -Using the ``http://pylonshq.com/pyramid`` namespace as the default XML -namespace isn't strictly necessary; you can use a different default -namespace as the default. However, if you do, the declaration tags -which are defined by :app:`Pyramid` such as the ``view`` declaration -tag will need to be defined in such a way that the XML parser that -:app:`Pyramid` uses knows which namespace the :mod:`pyramid` tags are -associated with. For example, the following files are all completely -equivalent: - -.. topic:: Use of A Non-Default XML Namespace - - .. code-block:: xml - :linenos: - - <configure xmlns="http://namespaces.zope.org/zope" - xmlns:pyramid="http://pylonshq.com/pyramid"> - - <include package="pyramid.includes" /> - - <pyramid:view - view="helloworld.hello_world" - /> - - </configure> - -.. topic:: Use of A Per-Tag XML Namespace Without A Default XML Namespace - - .. code-block:: xml - :linenos: - - <configure> - - <include package="pyramid.includes" /> - - <view xmlns="http://pylonshq.com/pyramid" - view="helloworld.hello_world" - /> - - </configure> - -For more information about XML namespaces, see `this older, but simple -XML.com article <http://www.xml.com/pub/a/1999/01/namespaces.html>`_. - -The conventions in this document assume that the default XML namespace -is ``http://pylonshq.com/pyramid``. - -Alternatives -~~~~~~~~~~~~ - -None. - -See Also -~~~~~~~~ - -See also :ref:`helloworld_declarative`. - diff --git a/docs/zcml/default_permission.rst b/docs/zcml/default_permission.rst deleted file mode 100644 index 54e720ea6..000000000 --- a/docs/zcml/default_permission.rst +++ /dev/null @@ -1,57 +0,0 @@ -.. _default_permission_directive: - -``default_permission`` -------------------------------- - -Set the default permission to be used by all :term:`view -configuration` registrations. - -This directive accepts a single attribute ,``name``, which should be -used as the default permission string. An example of a permission -string: ``view``. Adding a default permission makes it unnecessary to -protect each view configuration with an explicit permission, unless -your application policy requires some exception for a particular view. - -If a default permission is *not* set, views represented by view -configuration registrations which do not explicitly declare a -permission will be executable by entirely anonymous users (any -authorization policy is ignored). - -There can be only one default permission active at a time within an -application, thus the ``default_permission`` directive can only be -used once in any particular set of ZCML. - -Attributes -~~~~~~~~~~ - -``name`` - Must be a string representing a :term:`permission`, - e.g. ``view``. - - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <default_permission - name="view" - /> - -Alternatives -~~~~~~~~~~~~ - -Using the ``default_permission`` argument to the -:class:`pyramid.config.Configurator` constructor can be used -to achieve the same purpose. - -Using the -:meth:`pyramid.config.Configurator.set_default_permission` -method can be used to achieve the same purpose when using imperative -configuration. - -See Also -~~~~~~~~ - -See also :ref:`setting_a_default_permission`. diff --git a/docs/zcml/forbidden.rst b/docs/zcml/forbidden.rst deleted file mode 100644 index 70f65069e..000000000 --- a/docs/zcml/forbidden.rst +++ /dev/null @@ -1,78 +0,0 @@ -.. _forbidden_directive: - -``forbidden`` -------------- - -When :app:`Pyramid` can't authorize execution of a view based on -the :term:`authorization policy` in use, it invokes a :term:`forbidden -view`. The default forbidden response has a 401 status code and is -very plain, but it can be overridden as necessary using the -``forbidden`` ZCML directive. - -.. warning:: - - The ``forbidden`` ZCML directive is deprecated in :app:`Pyramid` - version 1.3. Instead, you should use the :ref:`view_directive` - directive with a ``context`` that names the - :exc:`pyramid.exceptions.Forbidden` class. See - :ref:`changing_the_forbidden_view` form more information. - -Attributes -~~~~~~~~~~ - -``view`` - The :term:`dotted Python name` to a :term:`view callable`. This - attribute is required unless a ``renderer`` attribute also exists. - If a ``renderer`` attribute exists on the directive, this attribute - defaults to a view that returns an empty dictionary (see - :ref:`views_which_use_a_renderer`). - -``attr`` - The attribute of the view callable to use if ``__call__`` is not - correct (has the same meaning as in the context of - :ref:`view_directive`; see the description of ``attr`` - there). - -``renderer`` - This is either a single string term (e.g. ``json``) or a string - implying a path or :term:`asset specification` - (e.g. ``templates/views.pt``) used when the view returns a - non-:term:`response` object. This attribute has the same meaning as - it would in the context of :ref:`view_directive`; see the - description of ``renderer`` there). - -``wrapper`` - The :term:`view name` (*not* an object dotted name) of another view - declared elsewhere in ZCML (or via the ``@view_config`` decorator) - which will receive the response body of this view as the - ``request.wrapped_body`` attribute of its own request, and the - response returned by this view as the ``request.wrapped_response`` - attribute of its own request. This attribute has the same meaning - as it would in the context of :ref:`view_directive`; see the - description of ``wrapper`` there). Note that the wrapper view - *should not* be protected by any permission; behavior is undefined - if it does. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <forbidden - view="helloworld.views.forbidden_view"/> - -Alternatives -~~~~~~~~~~~~ - -Use the :ref:`view_directive` directive with a ``context`` that names -the :exc:`pyramid.exceptions.Forbidden` class. - -Use the :meth:`pyramid.config.Configurator.add_view` method, -passing it a ``context`` which is the -:exc:`pyramid.exceptions.Forbidden` class. - -See Also -~~~~~~~~ - -See also :ref:`changing_the_forbidden_view`. diff --git a/docs/zcml/handler.rst b/docs/zcml/handler.rst deleted file mode 100644 index 01d442ab6..000000000 --- a/docs/zcml/handler.rst +++ /dev/null @@ -1,158 +0,0 @@ -.. _handler_directive: - -``handler`` ------------ - -The ``handler`` directive adds the configuration of a :term:`view handler` to -the :term:`application registry`. - -Attributes -~~~~~~~~~~ - -``route_name`` - The name of the route, e.g. ``myroute``. This attribute is required. It - must be unique among all defined handler and route names in a given - configuration. - -``pattern`` - The pattern of the route e.g. ``ideas/{idea}``. This attribute is - required. See :ref:`route_pattern_syntax` for information about the syntax - of route patterns. The name ``{action}`` is treated specially in handler - patterns. See :ref:`using_add_handler` for a discussion of how - ``{action}`` in handler patterns is treated. - -``action`` - If the action name is not specified in the ``pattern``, use this name as the - handler action (method name). - -``factory`` - The :term:`dotted Python name` to a function that will generate a - :app:`Pyramid` context object when the associated route matches. - e.g. ``mypackage.resources.MyResource``. If this argument is not - specified, a default root factory will be used. - -``xhr`` - This value should be either ``True`` or ``False``. If this value is - specified and is ``True``, the :term:`request` must possess an - ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this - route to match. This is useful for detecting AJAX requests issued - from jQuery, Prototype and other Javascript libraries. If this - predicate returns false, route matching continues. - -``traverse`` - If you would like to cause the :term:`context` to be something other - than the :term:`root` object when this route matches, you can spell - a traversal pattern as the ``traverse`` argument. This traversal - pattern will be used as the traversal path: traversal will begin at - the root object implied by this route (either the global root, or - the object returned by the ``factory`` associated with this route). - - The syntax of the ``traverse`` argument is the same as it is for - ``pattern``. For example, if the ``pattern`` provided to the - ``route`` directive is ``articles/{article}/edit``, and the - ``traverse`` argument provided to the ``route`` directive is - ``/{article}``, when a request comes in that causes the route to - match in such a way that the ``article`` match value is '1' (when - the request URI is ``/articles/1/edit``), the traversal path will be - generated as ``/1``. This means that the root object's - ``__getitem__`` will be called with the name ``1`` during the - traversal phase. If the ``1`` object exists, it will become the - :term:`context` of the request. :ref:`traversal_chapter` has more - information about traversal. - - If the traversal path contains segment marker names which are not - present in the ``pattern`` argument, a runtime error will occur. - The ``traverse`` pattern should not contain segment markers that do - not exist in the ``pattern``. - - A similar combining of routing and traversal is available when a - route is matched which contains a ``*traverse`` remainder marker in - its ``pattern`` (see :ref:`using_traverse_in_a_route_pattern`). The - ``traverse`` argument to the ``route`` directive allows you to - associate route patterns with an arbitrary traversal path without - using a a ``*traverse`` remainder marker; instead you can use other - match information. - - Note that the ``traverse`` argument to the ``handler`` directive is - ignored when attached to a route that has a ``*traverse`` remainder - marker in its pattern. - -``request_method`` - A string representing an HTTP method name, e.g. ``GET``, ``POST``, - ``HEAD``, ``DELETE``, ``PUT``. If this argument is not specified, - this route will match if the request has *any* request method. If - this predicate returns false, route matching continues. - -``path_info`` - The value of this attribute represents a regular expression pattern - that will be tested against the ``PATH_INFO`` WSGI environment - variable. If the regex matches, this predicate will be true. If - this predicate returns false, route matching continues. - -``request_param`` - This value can be any string. A view declaration with this - attribute ensures that the associated route will only match when the - request has a key in the ``request.params`` dictionary (an HTTP - ``GET`` or ``POST`` variable) that has a name which matches the - supplied value. If the value supplied to the attribute has a ``=`` - sign in it, e.g. ``request_params="foo=123"``, then the key - (``foo``) must both exist in the ``request.params`` dictionary, and - the value must match the right hand side of the expression (``123``) - for the route to "match" the current request. If this predicate - returns false, route matching continues. - -``header`` - The value of this attribute represents an HTTP header name or a - header name/value pair. If the value contains a ``:`` (colon), it - will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*`` - or ``Host:localhost``). The *value* of an attribute that represent - a name/value pair should be a regular expression. If the value does - not contain a colon, the entire value will be considered to be the - header name (e.g. ``If-Modified-Since``). If the value evaluates to - a header name only without a value, the header specified by the name - must be present in the request for this predicate to be true. If - the value evaluates to a header name/value pair, the header - specified by the name must be present in the request *and* the - regular expression specified as the value must match the header - value. Whether or not the value represents a header name or a - header name/value pair, the case of the header name is not - significant. If this predicate returns false, route matching - continues. - -``accept`` - The value of this attribute represents a match query for one or more - mimetypes in the ``Accept`` HTTP request header. If this value is - specified, it must be in one of the following forms: a mimetype - match token in the form ``text/plain``, a wildcard mimetype match - token in the form ``text/*`` or a match-all wildcard mimetype match - token in the form ``*/*``. If any of the forms matches the - ``Accept`` header of the request, this predicate will be true. If - this predicate returns false, route matching continues. - -``custom_predicates`` - This value should be a sequence of references to custom predicate - callables. Use custom predicates when no set of predefined - predicates does what you need. Custom predicates can be combined - with predefined predicates as necessary. Each custom predicate - callable should accept two arguments: ``info`` and ``request`` - and should return either ``True`` or ``False`` after doing arbitrary - evaluation of the info and/or the request. If all custom and - non-custom predicate callables return ``True`` the associated route - will be considered viable for a given request. If any predicate - callable returns ``False``, route matching continues. Note that the - value ``info`` passed to a custom route predicate is a dictionary - containing matching information; see :ref:`custom_route_predicates` - for more information about ``info``. - - -Alternatives -~~~~~~~~~~~~ - -You can also add a :term:`route configuration` via: - -- Using the :meth:`pyramid.config.Configurator.add_handler` method. - -See Also -~~~~~~~~ - -See also :ref:`handlers_chapter`. diff --git a/docs/zcml/include.rst b/docs/zcml/include.rst deleted file mode 100644 index f55caa07c..000000000 --- a/docs/zcml/include.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. _include_directive: - -``include`` ------------ - -The ``include`` directive includes configuration from an external ZCML -file. Use of the ``include`` tag allows a user to split configuration -across multiple ZCML files, and allows package distributors to provide -default ZCML configuration for specific purposes which can be -included by the integrator of the package as necessary. - -Attributes -~~~~~~~~~~ - -``package`` - A :term:`dotted Python name` which references a Python :term:`package`. - -``file`` - An absolute or relative filename which references a ZCML file. - -The ``package`` and ``file`` attributes can be used together or -separately as necessary. - -Examples -~~~~~~~~ - -.. topic:: Loading the File Named ``configure.zcml`` from a Package Implicitly - - .. code-block:: xml - :linenos: - - <include package="some.package" /> - -.. topic:: Loading the File Named ``other.zcml`` From the Current Package - - .. code-block:: xml - :linenos: - - <include file="other.zcml" /> - -.. topic:: Loading a File From a Subdirectory of the Current Package - - .. code-block:: xml - :linenos: - - <include file="subdir/other.zcml" /> - -.. topic:: Loading the File Named ``/absolute/path/other.zcml`` - - .. code-block:: xml - :linenos: - - <include file="/absolute/path/other.zcml" /> - -.. topic:: Loading the File Named ``other.zcml`` From a Package Explicitly - - .. code-block:: xml - :linenos: - - <include package="some.package" file="other.zcml" /> - -Alternatives -~~~~~~~~~~~~ - -None. - -See Also -~~~~~~~~ - -See also :ref:`helloworld_declarative`. - diff --git a/docs/zcml/localenegotiator.rst b/docs/zcml/localenegotiator.rst deleted file mode 100644 index c90e649f6..000000000 --- a/docs/zcml/localenegotiator.rst +++ /dev/null @@ -1,39 +0,0 @@ -.. _localenegotiator_directive: - -``localenegotiator`` --------------------- - -Set the :term:`locale negotiator` for the current configurator to -support localization of text. - -Attributes -~~~~~~~~~~ - -``negotiator`` - - The :term:`dotted Python name` to a :term:`locale negotiator` - implementation. This attribute is required. If it begins with a - dot (``.``), the name will be considered relative to the directory - in which the ZCML file which contains this directive lives. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <localenegotiator - negotiator="some.package.module.my_locale_negotiator" - /> - -Alternatives -~~~~~~~~~~~~ - -Use :meth:`pyramid.config.Configurator.set_locale_negotiator` -method instance during initial application setup. - -See Also -~~~~~~~~ - -See also :ref:`activating_translation`. - diff --git a/docs/zcml/notfound.rst b/docs/zcml/notfound.rst deleted file mode 100644 index 739eccd49..000000000 --- a/docs/zcml/notfound.rst +++ /dev/null @@ -1,78 +0,0 @@ -.. _notfound_directive: - -``notfound`` ------------- - -.. warning:: - - The ``notfound`` ZCML directive is deprecated in :app:`Pyramid` - version 1.0. Instead, you should use the :ref:`view_directive` - directive with a ``context`` that names the - :exc:`pyramid.exceptions.NotFound` class. See - :ref:`changing_the_notfound_view` form more information. - -When :app:`Pyramid` can't map a URL to view code, it invokes a -:term:`not found view`. The default not found view is very plain, but -the view callable used can be configured via the ``notfound`` ZCML -tag. - -Attributes -~~~~~~~~~~ - -``view`` - The :term:`dotted Python name` to a :term:`view callable`. This - attribute is required unless a ``renderer`` attribute also exists. - If a ``renderer`` attribute exists on the directive, this attribute - defaults to a view that returns an empty dictionary (see - :ref:`views_which_use_a_renderer`). - -``attr`` - The attribute of the view callable to use if ``__call__`` is not - correct (has the same meaning as in the context of - :ref:`view_directive`; see the description of ``attr`` - there). - -``renderer`` - This is either a single string term (e.g. ``json``) or a string - implying a path or :term:`asset specification` - (e.g. ``templates/views.pt``) used when the view returns a - non-:term:`response` object. This attribute has the same meaning as - it would in the context of :ref:`view_directive`; see the - description of ``renderer`` there). - -``wrapper`` - The :term:`view name` (*not* an object dotted name) of another view - declared elsewhere in ZCML (or via the ``@view_config`` decorator) - which will receive the response body of this view as the - ``request.wrapped_body`` attribute of its own request, and the - response returned by this view as the ``request.wrapped_response`` - attribute of its own request. This attribute has the same meaning - as it would in the context of :ref:`view_directive`; see - the description of ``wrapper`` there). Note that the wrapper view - *should not* be protected by any permission; behavior is undefined - if it does. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <notfound - view="helloworld.views.notfound_view"/> - -Alternatives -~~~~~~~~~~~~ - -Use the :ref:`view_directive` directive with a ``context`` that names -the :exc:`pyramid.exceptions.NotFound` class. - -Use the :meth:`pyramid.config.Configurator.add_view` method, -passing it a ``context`` which is the -:exc:`pyramid.exceptions.NotFound` class. - -See Also -~~~~~~~~ - -See also :ref:`changing_the_notfound_view`. - diff --git a/docs/zcml/remoteuserauthenticationpolicy.rst b/docs/zcml/remoteuserauthenticationpolicy.rst deleted file mode 100644 index 56e73ee7a..000000000 --- a/docs/zcml/remoteuserauthenticationpolicy.rst +++ /dev/null @@ -1,51 +0,0 @@ -.. _remoteuserauthenticationpolicy_directive: - -``remoteuserauthenticationpolicy`` ----------------------------------- - -When this directive is used, authentication information is obtained -from a ``REMOTE_USER`` key in the WSGI environment, assumed to -be set by a WSGI server or an upstream middleware component. - -Attributes -~~~~~~~~~~ - -``environ_key`` - The ``environ_key`` is the name that will be used to obtain the - remote user value from the WSGI environment. It defaults to - ``REMOTE_USER``. - -``callback`` - The ``callback`` is a Python dotted name to a function passed the - string representing the remote user and the request as positional - arguments. The callback is expected to return None if the user - represented by the string doesn't exist or a sequence of group - identifiers (possibly empty) if the user does exist. If - ``callback`` is None, the userid will be assumed to exist with no - groups. It defaults to ``None``. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <remoteuserauthenticationpolicy - environ_key="REMOTE_USER" - callback=".somemodule.somefunc" - /> - -Alternatives -~~~~~~~~~~~~ - -You may create an instance of the -:class:`pyramid.authentication.RemoteUserAuthenticationPolicy` and -pass it to the :class:`pyramid.config.Configurator` -constructor as the ``authentication_policy`` argument during initial -application configuration. - -See Also -~~~~~~~~ - -See also :ref:`authentication_policies_directives_section` and -:class:`pyramid.authentication.RemoteUserAuthenticationPolicy`. diff --git a/docs/zcml/renderer.rst b/docs/zcml/renderer.rst deleted file mode 100644 index c7beead32..000000000 --- a/docs/zcml/renderer.rst +++ /dev/null @@ -1,51 +0,0 @@ -.. _renderer_directive: - -``renderer`` ------------- - -The ``renderer`` ZCML directive can be used to override an existing -existing :term:`renderer` or to add a new renderer. - -Attributes -~~~~~~~~~~ - -``factory`` - A :term:`dotted Python name` referencing a callable object that - accepts a renderer name and returns a :term:`renderer` object. - -``name`` - The renderer name, which is a string. - -Examples -~~~~~~~~ - -.. topic:: Registering a Non-Template Renderer - - .. code-block:: xml - :linenos: - - <renderer - factory="some.renderer" - name="mynewrenderer" - /> - -.. topic:: Registering a Template Renderer - - .. code-block:: xml - :linenos: - - <renderer - factory="some.jinja2.renderer" - name=".jinja2" - /> - -Alternatives -~~~~~~~~~~~~ - -The :meth:`pyramid.config.Configurator.add_renderer` method -is equivalent to the ``renderer`` ZCML directive. - -See Also -~~~~~~~~ - -See also :ref:`adding_and_overriding_renderers`. diff --git a/docs/zcml/repozewho1authenticationpolicy.rst b/docs/zcml/repozewho1authenticationpolicy.rst deleted file mode 100644 index 11907ce31..000000000 --- a/docs/zcml/repozewho1authenticationpolicy.rst +++ /dev/null @@ -1,53 +0,0 @@ -.. _repozewho1authenticationpolicy_directive: - -``repozewho1authenticationpolicy`` ----------------------------------- - -When this directive is used, authentication information is obtained -from a ``repoze.who.identity`` key in the WSGI environment, assumed to -be set by :term:`repoze.who` middleware. - -Attributes -~~~~~~~~~~ - -``identifier_name`` - The ``identifier_name`` controls the name used to look up the - :term:`repoze.who` "identifier" plugin within - ``request.environ['repoze.who.plugins']`` which is used by this - policy to "remember" and "forget" credentials. It defaults to - ``auth_tkt``. - -``callback`` - The ``callback`` is a Python dotted name to a function passed the - repoze.who identity and the request as positional arguments. The - callback is expected to return None if the user represented by the - identity doesn't exist or a sequence of group identifiers - (possibly empty) if the user does exist. If ``callback`` is None, - the userid will be assumed to exist with no groups. It defaults - to ``None``. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <repozewho1authenticationpolicy - identifier_name="auth_tkt" - callback=".somemodule.somefunc" - /> - -Alternatives -~~~~~~~~~~~~ - -You may create an instance of the -:class:`pyramid.authentication.RepozeWho1AuthenticationPolicy` and -pass it to the :class:`pyramid.config.Configurator` -constructor as the ``authentication_policy`` argument during initial -application configuration. - -See Also -~~~~~~~~ - -See also :ref:`authentication_policies_directives_section` and -:class:`pyramid.authentication.RepozeWho1AuthenticationPolicy`. diff --git a/docs/zcml/route.rst b/docs/zcml/route.rst deleted file mode 100644 index 0f94fa11b..000000000 --- a/docs/zcml/route.rst +++ /dev/null @@ -1,223 +0,0 @@ -.. _route_directive: - -``route`` ---------- - -The ``route`` directive adds a single :term:`route configuration` to -the :term:`application registry`. - -Attributes -~~~~~~~~~~ - -``pattern`` - The pattern of the route e.g. ``ideas/{idea}``. This attribute is - required. See :ref:`route_pattern_syntax` for information - about the syntax of route patterns. - - .. note:: For backwards compatibility purposes, the ``path`` - attribute can also be used instead of ``pattern``. - -``name`` - The name of the route, e.g. ``myroute``. This attribute is - required. It must be unique among all defined routes in a given - configuration. - -``factory`` - The :term:`dotted Python name` to a function that will generate a - :app:`Pyramid` context object when this route matches. - e.g. ``mypackage.resources.MyResource``. If this argument is not - specified, a default root factory will be used. - -``view`` - The :term:`dotted Python name` to a function that will be used as a - view callable when this route matches. - e.g. ``mypackage.views.my_view``. - -``xhr`` - This value should be either ``True`` or ``False``. If this value is - specified and is ``True``, the :term:`request` must possess an - ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this - route to match. This is useful for detecting AJAX requests issued - from jQuery, Prototype and other Javascript libraries. If this - predicate returns false, route matching continues. - -``traverse`` - If you would like to cause the :term:`context` to be something other - than the :term:`root` object when this route matches, you can spell - a traversal pattern as the ``traverse`` argument. This traversal - pattern will be used as the traversal path: traversal will begin at - the root object implied by this route (either the global root, or - the object returned by the ``factory`` associated with this route). - - The syntax of the ``traverse`` argument is the same as it is for - ``pattern``. For example, if the ``pattern`` provided to the - ``route`` directive is ``articles/{article}/edit``, and the - ``traverse`` argument provided to the ``route`` directive is - ``/{article}``, when a request comes in that causes the route to - match in such a way that the ``article`` match value is '1' (when - the request URI is ``/articles/1/edit``), the traversal path will be - generated as ``/1``. This means that the root object's - ``__getitem__`` will be called with the name ``1`` during the - traversal phase. If the ``1`` object exists, it will become the - :term:`context` of the request. :ref:`traversal_chapter` has more - information about traversal. - - If the traversal path contains segment marker names which are not - present in the ``pattern`` argument, a runtime error will occur. - The ``traverse`` pattern should not contain segment markers that do - not exist in the ``pattern``. - - A similar combining of routing and traversal is available when a - route is matched which contains a ``*traverse`` remainder marker in - its ``pattern`` (see :ref:`using_traverse_in_a_route_pattern`). The - ``traverse`` argument to the ``route`` directive allows you to - associate route patterns with an arbitrary traversal path without - using a a ``*traverse`` remainder marker; instead you can use other - match information. - - Note that the ``traverse`` argument to the ``route`` directive is - ignored when attached to a route that has a ``*traverse`` remainder - marker in its pattern. - -``request_method`` - A string representing an HTTP method name, e.g. ``GET``, ``POST``, - ``HEAD``, ``DELETE``, ``PUT``. If this argument is not specified, - this route will match if the request has *any* request method. If - this predicate returns false, route matching continues. - -``path_info`` - The value of this attribute represents a regular expression pattern - that will be tested against the ``PATH_INFO`` WSGI environment - variable. If the regex matches, this predicate will be true. If - this predicate returns false, route matching continues. - -``request_param`` - This value can be any string. A view declaration with this - attribute ensures that the associated route will only match when the - request has a key in the ``request.params`` dictionary (an HTTP - ``GET`` or ``POST`` variable) that has a name which matches the - supplied value. If the value supplied to the attribute has a ``=`` - sign in it, e.g. ``request_params="foo=123"``, then the key - (``foo``) must both exist in the ``request.params`` dictionary, and - the value must match the right hand side of the expression (``123``) - for the route to "match" the current request. If this predicate - returns false, route matching continues. - -``header`` - The value of this attribute represents an HTTP header name or a - header name/value pair. If the value contains a ``:`` (colon), it - will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*`` - or ``Host:localhost``). The *value* of an attribute that represent - a name/value pair should be a regular expression. If the value does - not contain a colon, the entire value will be considered to be the - header name (e.g. ``If-Modified-Since``). If the value evaluates to - a header name only without a value, the header specified by the name - must be present in the request for this predicate to be true. If - the value evaluates to a header name/value pair, the header - specified by the name must be present in the request *and* the - regular expression specified as the value must match the header - value. Whether or not the value represents a header name or a - header name/value pair, the case of the header name is not - significant. If this predicate returns false, route matching - continues. - -``accept`` - The value of this attribute represents a match query for one or more - mimetypes in the ``Accept`` HTTP request header. If this value is - specified, it must be in one of the following forms: a mimetype - match token in the form ``text/plain``, a wildcard mimetype match - token in the form ``text/*`` or a match-all wildcard mimetype match - token in the form ``*/*``. If any of the forms matches the - ``Accept`` header of the request, this predicate will be true. If - this predicate returns false, route matching continues. - -``custom_predicates`` - - This value should be a sequence of references to custom predicate - callables. Use custom predicates when no set of predefined - predicates does what you need. Custom predicates can be combined - with predefined predicates as necessary. Each custom predicate - callable should accept two arguments: ``info`` and ``request`` - and should return either ``True`` or ``False`` after doing arbitrary - evaluation of the info and/or the request. If all custom and - non-custom predicate callables return ``True`` the associated route - will be considered viable for a given request. If any predicate - callable returns ``False``, route matching continues. Note that the - value ``info`` passed to a custom route predicate is a dictionary - containing matching information; see :ref:`custom_route_predicates` - for more information about ``info``. - -``view_context`` - The :term:`dotted Python name` to a class or an interface that the - :term:`context` of the view should match for the view named by the - route to be used. This attribute is only useful if the ``view`` - attribute is used. If this attribute is not specified, the default - (``None``) will be used. - - If the ``view`` attribute is not provided, this attribute has no - effect. - - This attribute can also be spelled as ``view_for`` or ``for_``; - these are valid older spellings. - -``view_permission`` - The permission name required to invoke the view associated with this - route. e.g. ``edit``. (see :ref:`using_security_with_urldispatch` - for more information about permissions). - - If the ``view`` attribute is not provided, this attribute has no - effect. - - This attribute can also be spelled as ``permission``. - -``view_renderer`` - This is either a single string term (e.g. ``json``) or a string - implying a path or :term:`asset specification` - (e.g. ``templates/views.pt``). If the renderer value is a single - term (does not contain a dot ``.``), the specified term will be used - to look up a renderer implementation, and that renderer - implementation will be used to construct a response from the view - return value. If the renderer term contains a dot (``.``), the - specified term will be treated as a path, and the filename extension - of the last element in the path will be used to look up the renderer - implementation, which will be passed the full path. The renderer - implementation will be used to construct a response from the view - return value. See :ref:`views_which_use_a_renderer` for more - information. - - If the ``view`` attribute is not provided, this attribute has no - effect. - - This attribute can also be spelled as ``renderer``. - -``view_attr`` - The view machinery defaults to using the ``__call__`` method of the - view callable (or the function itself, if the view callable is a - function) to obtain a response dictionary. The ``attr`` value allows - you to vary the method attribute used to obtain the response. For - example, if your view was a class, and the class has a method named - ``index`` and you wanted to use this method instead of the class' - ``__call__`` method to return the response, you'd say - ``attr="index"`` in the view configuration for the view. This is - most useful when the view definition is a class. - - If the ``view`` attribute is not provided, this attribute has no - effect. - -``use_global_views`` - When a request matches this route, and view lookup cannot find a view - which has a 'route_name' predicate argument that matches the route, - try to fall back to using a view that otherwise matches the context, - request, and view name (but does not match the route name predicate). - -Alternatives -~~~~~~~~~~~~ - -You can also add a :term:`route configuration` via: - -- Using the :meth:`pyramid.config.Configurator.add_route` method. - -See Also -~~~~~~~~ - -See also :ref:`urldispatch_chapter`. diff --git a/docs/zcml/scan.rst b/docs/zcml/scan.rst deleted file mode 100644 index df2cdd281..000000000 --- a/docs/zcml/scan.rst +++ /dev/null @@ -1,34 +0,0 @@ -.. _scan_directive: - -``scan`` --------- - -To make use of :term:`configuration decoration` decorators, you must -perform a :term:`scan`. A scan finds these decorators in code. The -``scan`` ZCML directive tells :app:`Pyramid` to begin such a scan. - -Attributes -~~~~~~~~~~ - -``package`` - The package to scan or the single dot (``.``), meaning the - "current" package (the package in which the ZCML file lives). - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <scan package="."/> - -Alternatives -~~~~~~~~~~~~ - -The :meth:`pyramid.config.Configurator.scan` method performs -the same job as the ``scan`` ZCML directive. - -See Also -~~~~~~~~ - -See also :ref:`mapping_views_using_a_decorator_section`. diff --git a/docs/zcml/static.rst b/docs/zcml/static.rst deleted file mode 100644 index 9538d18f0..000000000 --- a/docs/zcml/static.rst +++ /dev/null @@ -1,89 +0,0 @@ -.. _static_directive: - -``static`` ----------- - -Use of the ``static`` ZCML directive or allows you to serve static -resources (such as JavaScript and CSS files) within a -:app:`Pyramid` application. This mechanism makes static files -available at a name relative to the application root URL. - -Attributes -~~~~~~~~~~ - -``name`` - The (application-root-relative) URL prefix of the static directory. - For example, to serve static files from ``/static`` in most - applications, you would provide a ``name`` of ``static``. - -``path`` - A path to a directory on disk where the static files live. This - path may either be 1) absolute (e.g. ``/foo/bar/baz``) 2) - Python-package-relative (e.g. (``packagename:foo/bar/baz``) or 3) - relative to the package directory in which the ZCML file which - contains the directive (e.g. ``foo/bar/baz``). - -``cache_max_age`` - The number of seconds that the static resource can be cached, as - represented in the returned response's ``Expires`` and/or - ``Cache-Control`` headers, when any static file is served from this - directive. This defaults to 3600 (5 minutes). Optional. - -``permission`` - Used to specify the :term:`permission` required by a user to execute - this static view. This value defaults to the string - ``__no_permission_required__``. The ``__no_permission_required__`` - string is a special sentinel which indicates that, even if a - :term:`default permission` exists for the current application, the - static view should be renderered to completely anonymous users. - This default value is permissive because, in most web apps, static - resources seldom need protection from viewing. You should use this - option only if you register a static view which points at a - directory that contains resources which should be shown only if the - calling user has (according to the :term:`authorization policy`) a - particular permission. - -Examples -~~~~~~~~ - -.. topic:: Serving Static Files from an Absolute Path - - .. code-block:: xml - :linenos: - - <static - name="static" - path="/var/www/static" - /> - -.. topic:: Serving Static Files from a Package-Relative Path - - .. code-block:: xml - :linenos: - - <static - name="static" - path="some_package:a/b/c/static" - /> - -.. topic:: Serving Static Files from a Current-Package-Relative Path - - .. code-block:: xml - :linenos: - - <static - name="static" - path="static_files" - /> - -Alternatives -~~~~~~~~~~~~ - -:meth:`pyramid.config.Configurator.add_static_view` can also -be used to add a static view. - -See Also -~~~~~~~~ - -See also :ref:`static_resources_section` and -:ref:`generating_static_resource_urls`. diff --git a/docs/zcml/subscriber.rst b/docs/zcml/subscriber.rst deleted file mode 100644 index 25c4abf2e..000000000 --- a/docs/zcml/subscriber.rst +++ /dev/null @@ -1,45 +0,0 @@ -.. _subscriber_directive: - -``subscriber`` --------------- - -The ``subscriber`` ZCML directive configures an :term:`subscriber` -callable to listen for events broadcast by the :app:`Pyramid` web -framework. - -Attributes -~~~~~~~~~~ - -``for`` - The class or :term:`interface` that you are subscribing the listener for, - e.g. :class:`pyramid.events.NewRequest`. Registering a subscriber for a - specific class or interface limits the event types that the subscriber - will receive to those specified by the interface or class. Default: - ``zope.interface.Interface`` (implying *any* event type). - -``handler`` - A :term:`dotted Python name` which references an event handler - callable. The callable should accept a single argument: ``event``. - The return value of the callable is ignored. - -Examples -~~~~~~~~ - -.. code-block:: xml - :linenos: - - <subscriber - for="pyramid.events.NewRequest" - handler=".subscribers.handle_new_request" - /> - -Alternatives -~~~~~~~~~~~~ - -You can also register an event listener by using the -:meth:`pyramid.config.Configurator.add_subscriber` method. - -See Also -~~~~~~~~ - -See also :ref:`events_chapter`. diff --git a/docs/zcml/translationdir.rst b/docs/zcml/translationdir.rst deleted file mode 100644 index 5cf615d26..000000000 --- a/docs/zcml/translationdir.rst +++ /dev/null @@ -1,64 +0,0 @@ -.. _translationdir_directive: - -``translationdir`` ------------------- - -Add a :term:`gettext` :term:`translation directory` to the current -configuration for use in localization of text. - -Attributes -~~~~~~~~~~ - -``dir`` - The path to the translation directory. This path may either be 1) - absolute (e.g. ``/foo/bar/baz``) 2) Python-package-relative - (e.g. ``packagename:foo/bar/baz``) or 3) relative to the package - directory in which the ZCML file which contains the directive - (e.g. ``foo/bar/baz``). - -Example 1 -~~~~~~~~~ - -.. code-block:: xml - :linenos: - - <!-- relative to configure.zcml file --> - - <translationdir - dir="locale" - /> - -Example 2 -~~~~~~~~~ - -.. code-block:: xml - :linenos: - - <!-- relative to another package --> - - <translationdir - dir="another.package:locale" - /> - -Example 3 -~~~~~~~~~ - -.. code-block:: xml - :linenos: - - <!-- an absolute directory name --> - - <translationdir - dir="/usr/share/locale" - /> - -Alternatives -~~~~~~~~~~~~ - -Use :meth:`pyramid.config.Configurator.add_translation_dirs` -method instance during initial application setup. - -See Also -~~~~~~~~ - -See also :ref:`activating_translation`. diff --git a/docs/zcml/utility.rst b/docs/zcml/utility.rst deleted file mode 100644 index 1341dfb83..000000000 --- a/docs/zcml/utility.rst +++ /dev/null @@ -1,46 +0,0 @@ -.. _utility_directive: - -``utility`` ------------ - -Register a :term:`Zope Component Architecture` "utility". - -Attributes -~~~~~~~~~~ - -``component`` - The utility component (cannot be specified if ``factory`` is - specified). - -``factory`` - A factory that creates a component (cannot be specified if - ``component`` is specified). - -``provides`` - The :term:`interface` that an utility instance resulting from a - lookup will provide. - -``name`` - The utility name. - -Example -~~~~~~~ - -.. code-block:: xml - :linenos: - - <utility - provides=".interfaces.IMyUtility" - component=".utilities.MyUtility" - /> - -Alternatives -~~~~~~~~~~~~ - -Use the ``registerUtility`` method of the ``registry`` attribute of a -:term:`Configurator` instance during initial application setup. - -See Also -~~~~~~~~ - -None. diff --git a/docs/zcml/view.rst b/docs/zcml/view.rst deleted file mode 100644 index b4fabdc2c..000000000 --- a/docs/zcml/view.rst +++ /dev/null @@ -1,252 +0,0 @@ -.. _view_directive: - -``view`` --------- - -A ``view`` declaration directs :app:`Pyramid` to create a single -:term:`view configuration` registration in the current -:term:`application registry`. - -The ``view`` ZCML directive has many possible attributes. Some of the -attributes are descriptive or influence rendering. Other attributes -are :term:`predicate` attributes, meaning that they imply an -evaluation to true or false when view lookup is performed. - -*All* predicates named in a view configuration must evaluate to true -in order for the view callable it names to be considered "invokable" -for a given request. See :ref:`view_lookup` for a description of how -a view configuration matches (or doesn't match) during a request. - -The possible attributes of the ``view`` ZCML directive are described -below. They are divided into predicate and non-predicate categories. - -Attributes -~~~~~~~~~~ - -Non-Predicate Attributes -######################## - -``view`` - The :term:`dotted Python name` to a :term:`view callable`. This - attribute is required unless a ``renderer`` attribute also exists. - If a ``renderer`` attribute exists on the directive, this attribute - defaults to a view that returns an empty dictionary (see - :ref:`views_which_use_a_renderer`). - -``permission`` - The name of a *permission* that the user must possess in order to - call the view. See :ref:`view_security_section` for more - information about view security and permissions. - -``attr`` - The view machinery defaults to using the ``__call__`` method of the - view callable (or the function itself, if the view callable is a - function) to obtain a response dictionary. The ``attr`` value - allows you to vary the method attribute used to obtain the response. - For example, if your view was a class, and the class has a method - named ``index`` and you wanted to use this method instead of the - class' ``__call__`` method to return the response, you'd say - ``attr="index"`` in the view configuration for the view. This is - most useful when the view definition is a class. - -``renderer`` - This is either a single string term (e.g. ``json``) or a string - implying a path or :term:`asset specification` - (e.g. ``templates/views.pt``). If the renderer value is a single - term (does not contain a dot ``.``), the specified term will be used - to look up a renderer implementation, and that renderer - implementation will be used to construct a response from the view - return value. If the renderer term contains a dot (``.``), the - specified term will be treated as a path, and the filename extension - of the last element in the path will be used to look up the renderer - implementation, which will be passed the full path. The renderer - implementation will be used to construct a response from the view - return value. - - Note that if the view itself returns a response (see - :ref:`the_response`), the specified renderer implementation is never - called. - - When the renderer is a path, although a path is usually just a - simple relative pathname (e.g. ``templates/foo.pt``, implying that a - template named "foo.pt" is in the "templates" directory relative to - the directory in which the ZCML file is defined), a path can be - absolute, starting with a slash on UNIX or a drive letter prefix on - Windows. The path can alternately be a :term:`asset - specification` in the form - ``some.dotted.package_name:relative/path``, making it possible to - address template assets which live in a separate package. - - The ``renderer`` attribute is optional. If it is not defined, the - "null" renderer is assumed (no rendering is performed and the value - is passed back to the upstream BFG machinery unmolested). - -``wrapper`` - The :term:`view name` (*not* an object dotted name) of another view - declared elsewhere in ZCML (or via the ``@view_config`` decorator) - which will receive the response body of this view as the - ``request.wrapped_body`` attribute of its own request, and the - response returned by this view as the ``request.wrapped_response`` - attribute of its own request. Using a wrapper makes it possible to - "chain" views together to form a composite response. The response - of the outermost wrapper view will be returned to the user. The - wrapper view will be found as any view is found: see - :ref:`view_lookup`. The "best" wrapper view will be found based on - the lookup ordering: "under the hood" this wrapper view is looked up - via ``pyramid.view.render_view_to_response(context, request, - 'wrapper_viewname')``. The context and request of a wrapper view is - the same context and request of the inner view. If this attribute - is unspecified, no view wrapping is done. - -Predicate Attributes -#################### - -``name`` - The *view name*. Read the :ref:`traversal_chapter` to understand - the concept of a view name. - -``context`` - A :term:`dotted Python name` representing the Python class that the - :term:`context` must be an instance of, *or* the :term:`interface` - that the :term:`context` must provide in order for this view to be - found and called. This predicate is true when the :term:`context` - is an instance of the represented class or if the :term:`context` - provides the represented interface; it is otherwise false. An - alternate name for this attribute is ``for`` (this is an older - spelling). - -``route_name`` - *This attribute services an advanced feature that isn't often used - unless you want to perform traversal after a route has matched.* - This value must match the ``name`` of a ``<route>`` declaration (see - :ref:`urldispatch_chapter`) that must match before this view will be - called. Note that the ``route`` configuration referred to by - ``route_name`` usually has a ``*traverse`` token in the value of its - ``path``, representing a part of the path that will be used by - traversal against the result of the route's :term:`root factory`. - See :ref:`hybrid_chapter` for more information on using this - advanced feature. - -``request_type`` - This value should be a :term:`dotted Python name` string - representing the :term:`interface` that the :term:`request` must - have in order for this view to be found and called. The presence of - this attribute is largely for backwards compatibility with - older iterations of this framework. - -``request_method`` - This value can either be one of the strings 'GET', 'POST', 'PUT', - 'DELETE', or 'HEAD' representing an HTTP ``REQUEST_METHOD``. A view - declaration with this attribute ensures that the view will only be - called when the request's ``method`` (aka ``REQUEST_METHOD``) string - matches the supplied value. - -``request_param`` - This value can be any string. A view declaration with this - attribute ensures that the view will only be called when the request - has a key in the ``request.params`` dictionary (an HTTP ``GET`` or - ``POST`` variable) that has a name which matches the supplied value. - If the value supplied to the attribute has a ``=`` sign in it, - e.g. ``request_params="foo=123"``, then the key (``foo``) must both - exist in the ``request.params`` dictionary, and the value must match - the right hand side of the expression (``123``) for the view to - "match" the current request. - -``containment`` - This value should be a :term:`dotted Python name` string - representing the class that a graph traversal parent object of the - :term:`context` must be an instance of (or :term:`interface` that a - parent object must provide) in order for this view to be found and - called. Your resources must be "location-aware" to use this feature. - See :ref:`location_aware` for more information about - location-awareness. - -``xhr`` - This value should be either ``True`` or ``False``. If this value is - specified and is ``True``, the :term:`request` must possess an - ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header that has - the value ``XMLHttpRequest`` for this view to be found and called. - This is useful for detecting AJAX requests issued from jQuery, - Prototype and other Javascript libraries. - -``accept`` - The value of this attribute represents a match query for one or more - mimetypes in the ``Accept`` HTTP request header. If this value is - specified, it must be in one of the following forms: a mimetype - match token in the form ``text/plain``, a wildcard mimetype match - token in the form ``text/*`` or a match-all wildcard mimetype match - token in the form ``*/*``. If any of the forms matches the - ``Accept`` header of the request, this predicate will be true. - -``header`` - The value of this attribute represents an HTTP header name or a - header name/value pair. If the value contains a ``:`` (colon), it - will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*`` - or ``Host:localhost``). The *value* of an attribute that represent - a name/value pair should be a regular expression. If the value does - not contain a colon, the entire value will be considered to be the - header name (e.g. ``If-Modified-Since``). If the value evaluates to - a header name only without a value, the header specified by the name - must be present in the request for this predicate to be true. If - the value evaluates to a header name/value pair, the header - specified by the name must be present in the request *and* the - regular expression specified as the value must match the header - value. Whether or not the value represents a header name or a - header name/value pair, the case of the header name is not - significant. - -``path_info`` - The value of this attribute represents a regular expression pattern - that will be tested against the ``PATH_INFO`` WSGI environment - variable. If the regex matches, this predicate will be true. - -``custom_predicates`` - This value should be a sequence of references to custom predicate - callables (e.g. ``dotted.name.one dotted.name.two``, if used in - ZCML; a :term:`dotted Python name` to each callable separated by a - space). Use custom predicates when no set of predefined predicates - do what you need. Custom predicates can be combined with predefined - predicates as necessary. Each custom predicate callable should - accept two arguments: ``context`` and ``request`` and should return - either ``True`` or ``False`` after doing arbitrary evaluation of the - context and/or the request. If all callables return ``True``, the - associated view callable will be considered viable for a given - request. - -Examples -~~~~~~~~ - -.. topic:: Registering A Default View for a Class - - .. code-block:: xml - :linenos: - - <view - context=".resources.MyResource" - view=".views.hello_world" - /> - -.. topic:: Registering A View With a Predicate - - .. code-block:: xml - :linenos: - - <view - context=".resources.MyResource" - view=".views.hello_world_post" - request_method="POST" - /> - -Alternatives -~~~~~~~~~~~~ - -You can also add a :term:`view configuration` via: - -- Using the :class:`pyramid.view.view_config` class as a decorator. - -- Using the :meth:`pyramid.config.Configurator.add_view` method. - -See Also -~~~~~~~~ - -See also :ref:`views_chapter`. |
