diff options
Diffstat (limited to 'docs')
149 files changed, 3439 insertions, 1517 deletions
diff --git a/docs/.gitignore b/docs/.gitignore index 1e9e0413c..da7abd0c0 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,3 +1,4 @@ -_build _themes +_build + diff --git a/docs/Makefile b/docs/Makefile index 92dc56fda..bb381fc53 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -25,7 +25,7 @@ help: clean: -rm -rf _build/* -html: _themes +html: mkdir -p _build/html _build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @@ -47,7 +47,7 @@ pickle: web: pickle -htmlhelp: _themes +htmlhelp: mkdir -p _build/htmlhelp _build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @@ -84,5 +84,3 @@ epub: @echo @echo "Build finished. The epub file is in _build/epub." -_themes: - git clone git://github.com/Pylons/pylons_sphinx_theme.git _themes diff --git a/docs/api.rst b/docs/api.rst index 6ff6e9fb1..979e8f490 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -12,6 +12,7 @@ documentation is organized alphabetically by module name. api/authentication api/chameleon_text api/chameleon_zpt + api/compat api/config api/events api/exceptions @@ -20,6 +21,7 @@ documentation is organized alphabetically by module name. api/interfaces api/location api/paster + api/path api/registry api/renderers api/request diff --git a/docs/api/compat.rst b/docs/api/compat.rst new file mode 100644 index 000000000..bb34f38e4 --- /dev/null +++ b/docs/api/compat.rst @@ -0,0 +1,156 @@ +.. _compat_module: + +:mod:`pyramid.compat` +---------------------- + +The ``pyramid.compat`` module provides platform and version compatibility for +Pyramid and its add-ons across Python platform and version differences. APIs +will be removed from this module over time as Pyramid ceases to support +systems which require compatibility imports. + +.. automodule:: pyramid.compat + + .. autofunction:: ascii_native_ + + .. attribute:: binary_type + + Binary type for this platform. For Python 3, it's ``bytes``. For + Python 2, it's ``str``. + + .. autofunction:: bytes_ + + .. attribute:: class_types + + Sequence of class types for this platform. For Python 3, it's + ``(type,)``. For Python 2, it's ``(type, types.ClassType)``. + + .. attribute:: configparser + + On Python 2, the ``ConfigParser`` module, on Python 3, the + ``configparser`` module. + + .. function:: escape(v) + + On Python 2, the ``cgi.escape`` function, on Python 3, the + ``html.escape`` function. + + .. function:: exec_(code, globs=None, locs=None) + + Exec code in a compatible way on both Python 2 and 3. + + .. attribute:: im_func + + On Python 2, the string value ``im_func``, on Python 3, the string + value ``__func__``. + + .. function:: input_(v) + + On Python 2, the ``raw_input`` function, on Python 3, the + ``input`` function. + + .. attribute:: integer_types + + Sequence of integer types for this platform. For Python 3, it's + ``(int,)``. For Python 2, it's ``(int, long)``. + + .. function:: is_nonstr_iter(v) + + Return ``True`` if ``v`` is a non-``str`` iterable on both Python 2 and + Python 3. + + .. function:: iteritems_(d) + + Return ``d.items()`` on Python 3, ``d.iteritems()`` on Python 2. + + .. function:: itervalues_(d) + + Return ``d.values()`` on Python 3, ``d.itervalues()`` on Python 2. + + .. function:: iterkeys_(d) + + Return ``d.keys()`` on Python 3, ``d.iterkeys()`` on Python 2. + + .. attribute:: long + + Long type for this platform. For Python 3, it's ``int``. For + Python 2, it's ``long``. + + .. function:: map_(v) + + Return ``list(map(v))`` on Python 3, ``map(v)`` on Python 2. + + .. attribute:: pickle + + ``cPickle`` module if it exists, ``pickle`` module otherwise. + + .. attribute:: PY3 + + ``True`` if running on Python 3, ``False`` otherwise. + + .. attribute:: PYPY + + ``True`` if running on PyPy, ``False`` otherwise. + + .. function:: reraise(tp, value, tb=None) + + Reraise an exception in a compatible way on both Python 2 and Python 3, + e.g. ``reraise(*sys.exc_info())``. + + .. attribute:: string_types + + Sequence of string types for this platform. For Python 3, it's + ``(str,)``. For Python 2, it's ``(basestring,)``. + + .. attribute:: SimpleCookie + + On Python 2, the ``Cookie.SimpleCookie`` class, on Python 3, the + ``http.cookies.SimpleCookie`` module. + + .. autofunction:: text_ + + .. attribute:: text_type + + Text type for this platform. For Python 3, it's ``str``. For Python + 2, it's ``unicode``. + + .. autofunction:: native_ + + .. attribute:: urlparse + + ``urlparse`` module on Python 2, ``urllib.parse`` module on Python 3. + + .. attribute:: url_quote + + ``urllib.quote`` function on Python 2, ``urllib.parse.quote`` function + on Python 3. + + .. attribute:: url_quote_plus + + ``urllib.quote_plus`` function on Python 2, ``urllib.parse.quote_plus`` + function on Python 3. + + .. attribute:: url_unquote + + ``urllib.unquote`` function on Python 2, ``urllib.parse.unquote`` + function on Python 3. + + .. attribute:: url_encode + + ``urllib.urlencode`` function on Python 2, ``urllib.parse.urlencode`` + function on Python 3. + + .. attribute:: url_open + + ``urllib2.urlopen`` function on Python 2, ``urllib.request.urlopen`` + function on Python 3. + + .. function:: url_unquote_text(v, encoding='utf-8', errors='replace') + + On Python 2, return ``url_unquote(v).decode(encoding(encoding, errors))``; + on Python 3, return the result of ``urllib.parse.unquote``. + + .. function:: url_unquote_native(v, encoding='utf-8', errors='replace') + + On Python 2, return ``native_(url_unquote_text_v, encoding, errors))``; + on Python 3, return the result of ``urllib.parse.unquote``. + diff --git a/docs/api/config.rst b/docs/api/config.rst index a8c193b60..dbfbb1761 100644 --- a/docs/api/config.rst +++ b/docs/api/config.rst @@ -94,6 +94,23 @@ .. automethod:: set_renderer_globals_factory(factory) + .. attribute:: introspectable + + A shortcut attribute which points to the + :class:`pyramid.registry.Introspectable` class (used during + directives to provide introspection to actions). + + This attribute is new as of :app:`Pyramid` 1.3. + + .. attribute:: introspector + + The :term:`introspector` related to this configuration. It is an + instance implementing the :class:`pyramid.interfaces.IIntrospector` + interface. If the Configurator constructor was supplied with an + ``introspector`` argument, this attribute will be that value. + Otherwise, it will be an instance of a default introspector type. + + This attribute is new as of :app:`Pyramid` 1.3. .. attribute:: global_registries diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index b336e549d..5b190b53b 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -68,3 +68,11 @@ Other Interfaces .. autointerface:: IResponse :members: + .. autointerface:: IIntrospectable + :members: + + .. autointerface:: IIntrospector + :members: + + .. autointerface:: IActionInfo + :members: diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 2a32e07e9..3f7a1c364 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -5,13 +5,10 @@ .. automodule:: pyramid.paster - .. function:: get_app(config_uri, name=None) + .. autofunction:: bootstrap - Return the WSGI application named ``name`` in the PasteDeploy - config file specified by ``config_uri``. + .. autofunction:: get_app(config_uri, name=None) - If the ``name`` is None, this will attempt to parse the name from - the ``config_uri`` string expecting the format ``inifile#name``. - If no name is found, the name will default to "main". + .. autofunction:: get_appsettings(config_uri, name=None) - .. autofunction:: bootstrap + .. autofunction:: setup_logging(config_uri) diff --git a/docs/api/path.rst b/docs/api/path.rst new file mode 100644 index 000000000..d46c35d8e --- /dev/null +++ b/docs/api/path.rst @@ -0,0 +1,20 @@ +.. _path_module: + +:mod:`pyramid.path` +--------------------------- + +.. automodule:: pyramid.path + + .. attribute:: CALLER_PACKAGE + + A constant used by the constructor of + :class:`pyramid.path.DottedNameResolver` and + :class:`pyramid.path.AssetResolver` (see their docstrings for more + info). + + .. autoclass:: DottedNameResolver + :members: + + .. autoclass:: AssetResolver + :members: + diff --git a/docs/api/registry.rst b/docs/api/registry.rst index 4d327370a..e18d1b6c2 100644 --- a/docs/api/registry.rst +++ b/docs/api/registry.rst @@ -14,3 +14,34 @@ accessed as ``request.registry.settings`` or ``config.registry.settings`` in a typical Pyramid application. + .. attribute:: introspector + + When a registry is set up (or created) by a :term:`Configurator`, the + registry will be decorated with an instance named ``introspector`` + implementing the :class:`pyramid.interfaces.IIntrospector` interface. + See also :attr:`pyramid.config.Configurator.introspector``. + + When a registry is created "by hand", however, this attribute will not + exist until set up by a configurator. + + This attribute is often accessed as ``request.registry.introspector`` in + a typical Pyramid application. + + This attribute is new as of :app:`Pyramid` 1.3. + +.. class:: Introspectable + + The default implementation of the interface + :class:`pyramid.interfaces.IIntrospectable` used by framework exenders. + An instance of this class is is created when + :attr:`pyramid.config.Configurator.introspectable` is called. + + This class is new as of :app:`Pyramid` 1.3. + +.. class:: noop_introspector + + An introspector which throws away all registrations, useful for disabling + introspection altogether (pass as ``introspector`` to the + :term:`Configurator` constructor). + + This class is new as of :app:`Pyramid` 1.3. diff --git a/docs/api/settings.rst b/docs/api/settings.rst index ac1cd3f9c..6b12c038c 100644 --- a/docs/api/settings.rst +++ b/docs/api/settings.rst @@ -9,4 +9,6 @@ .. autofunction:: asbool + .. autofunction:: aslist + diff --git a/docs/api/view.rst b/docs/api/view.rst index 4dddea25f..9f59ddae7 100644 --- a/docs/api/view.rst +++ b/docs/api/view.rst @@ -16,6 +16,9 @@ .. autoclass:: view_config :members: + .. autoclass:: view_defaults + :members: + .. autoclass:: static :members: :inherited-members: diff --git a/docs/conf.py b/docs/conf.py index c78285840..9be5db325 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -39,20 +39,6 @@ LaTeXTranslator.depart_inline = nothing book = os.environ.get('BOOK') -# If your extensions are in another directory, add it here. If the directory -# is relative to the documentation root, use os.path.abspath to make it -# absolute, like shown here. -parent = os.path.dirname(os.path.dirname(__file__)) -sys.path.append(os.path.abspath(parent)) -wd = os.getcwd() -os.chdir(parent) -os.system('%s setup.py test -q' % sys.executable) -os.chdir(wd) - -for item in os.listdir(parent): - if item.endswith('.egg'): - sys.path.append(os.path.join(parent, item)) - # General configuration # --------------------- @@ -94,7 +80,7 @@ copyright = '%s, Agendaless Consulting' % datetime.datetime.now().year # other places throughout the built documents. # # The short X.Y version. -version = '1.2' +version = '1.4dev' # The full version, including alpha/beta/rc tags. release = version @@ -141,14 +127,42 @@ if book: # ----------------------- # Add and use Pylons theme -sys.path.append(os.path.abspath('_themes')) +if 'sphinx-build' in ' '.join(sys.argv): # protect against dumb importers + from subprocess import call, Popen, PIPE + + p = Popen('which git', shell=True, stdout=PIPE) + git = p.stdout.read().strip() + cwd = os.getcwd() + _themes = os.path.join(cwd, '_themes') + + if not os.path.isdir(_themes): + call([git, 'clone', 'git://github.com/Pylons/pylons_sphinx_theme.git', + '_themes']) + else: + os.chdir(_themes) + call([git, 'checkout', 'master']) + call([git, 'pull']) + os.chdir(cwd) + + sys.path.append(os.path.abspath('_themes')) + + parent = os.path.dirname(os.path.dirname(__file__)) + sys.path.append(os.path.abspath(parent)) + wd = os.getcwd() + os.chdir(parent) + os.system('%s setup.py test -q' % sys.executable) + os.chdir(wd) + + for item in os.listdir(parent): + if item.endswith('.egg'): + sys.path.append(os.path.join(parent, item)) + html_theme_path = ['_themes'] html_theme = 'pyramid' - -html_theme_options = { - 'github_url': 'https://github.com/Pylons/pyramid' -} - +html_theme_options = dict( + github_url='https://github.com/Pylons/pyramid', +# in_progress='true' + ) # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. @@ -461,7 +475,7 @@ def resig(app, what, name, obj, options, signature, return_annotation): # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. -epub_title = 'The Pyramid Web Application Development Framework, Version 1.2' +epub_title = 'The Pyramid Web Application Development Framework, Version 1.4dev' epub_author = 'Chris McDonough' epub_publisher = 'Agendaless Consulting' epub_copyright = '2008-2011' @@ -478,7 +492,7 @@ epub_scheme = 'ISBN' epub_identifier = '0615445675' # A unique identification for the text. -epub_uid = 'The Pyramid Web Application Development Framework, Version 1.2' +epub_uid = 'The Pyramid Web Application Development Framework, Version 1.4dev' # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. diff --git a/docs/glossary.rst b/docs/glossary.rst index fc282b2da..e4de15bd6 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -342,14 +342,13 @@ Glossary file. It was developed by Ian Bicking. Chameleon - `chameleon <http://chameleon.repoze.org>`_ is an attribute - language template compiler which supports both the :term:`ZPT` and - :term:`Genshi` templating specifications. It is written and - maintained by Malthe Borch. It has several extensions, such as - the ability to use bracketed (Genshi-style) ``${name}`` syntax, - even within ZPT. It is also much faster than the reference - implementations of both ZPT and Genshi. :app:`Pyramid` offers - Chameleon templating out of the box in ZPT and text flavors. + `chameleon <http://chameleon.repoze.org>`_ is an attribute language + template compiler which supports the :term:`ZPT` templating + specification. It is written and maintained by Malthe Borch. It has + several extensions, such as the ability to use bracketed (Mako-style) + ``${name}`` syntax. It is also much faster than the reference + implementation of ZPT. :app:`Pyramid` offers Chameleon templating out + of the box in ZPT and text flavors. ZPT The `Zope Page Template <http://wiki.zope.org/ZPT/FrontPage>`_ @@ -564,9 +563,8 @@ 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 + An individual method call made to a :term:`configuration directive`, + such as registering a :term:`view configuration` (via the :meth:`~pyramid.config.Configurator.add_view` method of the configurator) or :term:`route configuration` (via the :meth:`~pyramid.config.Configurator.add_route` method of the @@ -922,7 +920,7 @@ Glossary database information. :mod:`pyramid_debugtoolbar` is configured into the ``development.ini`` of all applications which use a Pyramid :term:`scaffold`. For more information, see - https://docs.pylonsproject.org/projects/pyramid_debugtoolbar/dev/ . + http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/dev/ . scaffold A project template that helps users get started writing a Pyramid @@ -935,5 +933,53 @@ Glossary used in production applications, because the logger can be configured to log to a file, to UNIX syslog, to the Windows Event Log, or even to email. See its `documentation - <https://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_. - + <http://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_. + + console script + A script written to the ``bin`` (on UNIX, or ``Scripts`` on Windows) + directory of a Python installation or virtualenv as the result of + running ``setup.py install`` or ``setup.py develop``. + + introspector + An object with the methods described by + :class:`pyramid.interfaces.IIntrospector` that is available in both + configuration code (for registration) and at runtime (for querying) that + allows a developer to introspect configuration statements and + relationships between those statements. + + conflict resolution + Pyramid attempts to resolve ambiguous configuration statements made by + application developers via automatic conflict resolution. Automatic + conflict resolution is described in + :ref:`automatic_conflict_resolution`. If Pyramid cannot resolve + ambiguous configuration statements, it is possible to manually resolve + them as described in :ref:`manually_resolving_conflicts`. + + configuration directive + A method of the :term:`Configurator` which causes a configuration action + to occur. The method :meth:`pyramid.config.Configurator.add_view` is a + configuration directive, and application developers can add their own + directives as necessary (see :ref:`add_directive`). + + action + Represents a pending configuration statement generated by a call to a + :term:`configuration directive`. The set of pending configuration + actions are processed when :meth:`pyramid.config.Configurator.commit` is + called. + + discriminator + The unique identifier of an :term:`action`. + + introspectable + An object which implements the attributes and methods described in + :class:`pyramid.interfaces.IIntrospectable`. Introspectables are used + by the :term:`introspector` to display configuration information about + a running Pyramid application. An introspectable is associated with a + :term:`action` by virtue of the + :meth:`pyramid.config.Configurator.action` method. + + asset descriptor + An instance representing an :term:`asset specification` provided by the + :meth:`pyramid.path.AssetResolver.resolve` method. It supports the + methods and attributes documented in + :class:`pyramid.interfaces.IAssetDescriptor`. diff --git a/docs/index.rst b/docs/index.rst index e4de8b0c8..df7a422d4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,6 +44,7 @@ Front Matter .. toctree:: :maxdepth: 1 + whatsnew-1.3 whatsnew-1.2 whatsnew-1.1 whatsnew-1.0 @@ -65,6 +66,7 @@ Narrative documentation in chapter form explaining how to use narr/configuration narr/project narr/startup + narr/router narr/urldispatch narr/views narr/renderers @@ -87,9 +89,10 @@ Narrative documentation in chapter form explaining how to use narr/security narr/hybrid narr/hooks - narr/advconfig + narr/introspector narr/extending - narr/router + narr/advconfig + narr/extconfig narr/threadlocals narr/zca diff --git a/docs/latexindex.rst b/docs/latexindex.rst index 584dd3825..4db5b64b2 100644 --- a/docs/latexindex.rst +++ b/docs/latexindex.rst @@ -31,6 +31,8 @@ Narrative Documentation narr/configuration narr/firstapp narr/project + narr/startup + narr/router narr/urldispatch narr/views narr/renderers @@ -53,9 +55,10 @@ Narrative Documentation narr/security narr/hybrid narr/hooks - narr/advconfig + narr/introspector narr/extending - narr/startup + narr/advconfig + narr/extconfig narr/threadlocals narr/zca diff --git a/docs/narr/MyProject/README.txt b/docs/narr/MyProject/README.txt index 5e10949fc..c28d0d94a 100644 --- a/docs/narr/MyProject/README.txt +++ b/docs/narr/MyProject/README.txt @@ -1,4 +1 @@ MyProject README - - - diff --git a/docs/narr/MyProject/development.ini b/docs/narr/MyProject/development.ini index 3a4758c44..d61da580f 100644 --- a/docs/narr/MyProject/development.ini +++ b/docs/narr/MyProject/development.ini @@ -41,6 +41,6 @@ level = NOTSET formatter = generic [formatter_generic] -format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s # End logging configuration diff --git a/docs/narr/MyProject/myproject/__init__.py b/docs/narr/MyProject/myproject/__init__.py index 04e219e36..31b02cf02 100644 --- a/docs/narr/MyProject/myproject/__init__.py +++ b/docs/narr/MyProject/myproject/__init__.py @@ -1,12 +1,10 @@ from pyramid.config import Configurator -from myproject.resources import Root def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ - config = Configurator(root_factory=Root, settings=settings) - config.add_view('myproject.views.my_view', - context='myproject.resources.Root', - renderer='myproject:templates/mytemplate.pt') - config.add_static_view('static', 'myproject:static') + config = Configurator(settings=settings) + config.add_static_view('static', 'static', cache_max_age=3600) + config.add_route('home', '/') + config.scan() return config.make_wsgi_app() diff --git a/docs/narr/MyProject/myproject/resources.py b/docs/narr/MyProject/myproject/resources.py deleted file mode 100644 index 3d811895c..000000000 --- a/docs/narr/MyProject/myproject/resources.py +++ /dev/null @@ -1,3 +0,0 @@ -class Root(object): - def __init__(self, request): - self.request = request diff --git a/docs/narr/MyProject/myproject/static/pylons.css b/docs/narr/MyProject/myproject/static/pylons.css index 33b21ac1a..c54499ddd 100644 --- a/docs/narr/MyProject/myproject/static/pylons.css +++ b/docs/narr/MyProject/myproject/static/pylons.css @@ -23,7 +23,7 @@ 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;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, @@ -31,19 +31,20 @@ body h2, body h3, body h4, body h5, -body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +body h6{font-family:"NeutonRegular","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:#000000;height:230px; -background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#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,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.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;} @@ -58,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]{width:205px;} +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;} diff --git a/docs/narr/MyProject/myproject/static/pyramid-small.png b/docs/narr/MyProject/myproject/static/pyramid-small.png Binary files differnew file mode 100644 index 000000000..a5bc0ade7 --- /dev/null +++ b/docs/narr/MyProject/myproject/static/pyramid-small.png diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/narr/MyProject/myproject/templates/mytemplate.pt index 97f1e1aa3..0bfac946e 100644 --- a/docs/narr/MyProject/myproject/templates/mytemplate.pt +++ b/docs/narr/MyProject/myproject/templates/mytemplate.pt @@ -1,42 +1,29 @@ -<!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.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" /> + <link rel="shortcut icon" href="/static/favicon.ico" /> + <link rel="stylesheet" href="/static/pylons.css" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> + <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> <!--[if lte IE 6]> - <link rel="stylesheet" - href="${request.static_url('myproject:static/ie6.css')}" - type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div> - <img src="${request.static_url('myproject:static/pyramid.png')}" - width="750" height="169" alt="pyramid"/> - </div> + <div><img src="/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/> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> the Pyramid web application development framework. </p> </div> @@ -45,62 +32,45 @@ <div class="bottom"> <div id="left" class="align-right"> <h2>Search documentation</h2> - <form method="get" - action="http://docs.pylonsproject.org/pyramid/current/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Go" /> - </form> + <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/current/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> + <a href="http://pylonsproject.org">Pylons Website</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/current/#narrative-documentation"> - Narrative Documentation - </a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#narrative-documentation">Narrative Documentation</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/current/#api-documentation"> - API Documentation - </a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#reference-material">API Documentation</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/current/#tutorials"> - Tutorials - </a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#tutorials">Tutorials</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/current/#change-history"> - Change History - </a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#detailed-change-history">Change History</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/current/#sample-applications"> - Sample Applications - </a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#sample-applications">Sample Applications</a> </li> <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/current/#support-and-development"> - Support and Development - </a> + <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#support-and-development">Support and Development</a> </li> <li> - <a href="irc://irc.freenode.net#pyramid"> - IRC Channel - </a> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> </li> - </ul> + </ul> </div> </div> </div> </div> <div id="footer"> - <div class="footer">© Copyright 2008-2010, Agendaless Consulting.</div> + <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> </div> </body> </html> diff --git a/docs/narr/MyProject/myproject/tests.py b/docs/narr/MyProject/myproject/tests.py index 5fa710278..d8b764041 100644 --- a/docs/narr/MyProject/myproject/tests.py +++ b/docs/narr/MyProject/myproject/tests.py @@ -10,9 +10,7 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from myproject.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'MyProject') - - diff --git a/docs/narr/MyProject/myproject/views.py b/docs/narr/MyProject/myproject/views.py index c43b34460..f571a5976 100644 --- a/docs/narr/MyProject/myproject/views.py +++ b/docs/narr/MyProject/myproject/views.py @@ -1,2 +1,5 @@ +from pyramid.view import view_config + +@view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): return {'project':'MyProject'} diff --git a/docs/narr/MyProject/production.ini b/docs/narr/MyProject/production.ini index 9d025715d..97050e8fe 100644 --- a/docs/narr/MyProject/production.ini +++ b/docs/narr/MyProject/production.ini @@ -25,11 +25,11 @@ keys = console keys = generic [logger_root] -level = INFO +level = WARN handlers = console [logger_myproject] -level = INFO +level = WARN handlers = qualname = myproject diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index 7b62b1a73..3a7bf2805 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -87,8 +87,8 @@ that ends something like this: 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)') + Line 14 of file app.py in <module>: 'config.add_view(hello_world)' + Line 17 of file app.py in <module>: 'config.add_view(goodbye_world)' This traceback is trying to tell us: @@ -115,6 +115,8 @@ Conflict detection happens for any kind of configuration: imperative configuration or configuration that results from the execution of a :term:`scan`. +.. _manually_resolving_conflicts: + Manually Resolving Conflicts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -397,76 +399,3 @@ 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. -.. index:: - single: add_directive - pair: configurator; adding directives - -.. _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/commandline.rst b/docs/narr/commandline.rst index 0dc41e919..66ef46671 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -111,6 +111,7 @@ For a URL that doesn't match any views, ``pviews`` will simply print out a single: interactive shell single: IPython single: pshell + single: bpython .. _interactive_shell: @@ -267,23 +268,26 @@ exposed, and the request is configured to generate urls from the host .. index:: single: IPython + single: bpython -IPython -~~~~~~~ +.. _ipython_or_bpython: + +IPython or bpython +~~~~~~~~~~~~~~~~~~ -If you have `IPython <http://en.wikipedia.org/wiki/IPython>`_ installed in -the interpreter you use to invoke the ``pshell`` command, ``pshell`` will use -an IPython interactive shell instead of a standard Python interpreter shell. -If you don't want this to happen, even if you have IPython installed, you can -pass the ``--disable-ipython`` flag to the ``pshell`` command to use a -standard Python interpreter shell unconditionally. +If you have `IPython <http://en.wikipedia.org/wiki/IPython>`_ or +`bpython <http://bpython-interpreter.org/>`_ or both installed in +the interpreter you use to invoke the ``pshell`` command, ``pshell`` will +autodiscover them and use the first respectively found in this order : +IPython, bpython, standard Python interpreter. However you could +specifically invoke one of your choice with the ``-p choice`` or +``--python-shell choice`` option. .. code-block:: text - [chrism@vitaminf shellenv]$ ../bin/pshell --disable-ipython \ + [chrism@vitaminf shellenv]$ ../bin/pshell -p ipython | bpython | python \ development.ini#MyProject - .. index:: pair: routes; printing single: proutes diff --git a/docs/narr/extconfig.rst b/docs/narr/extconfig.rst new file mode 100644 index 000000000..5e7fe2753 --- /dev/null +++ b/docs/narr/extconfig.rst @@ -0,0 +1,364 @@ +.. index:: + single: extending configuration + +.. _extconfig_narr: + +Extending Pyramid Configuration +=============================== + +Pyramid allows you to extend its Configurator with custom directives. Custom +directives can use other directives, they can add a custom :term:`action`, +they can participate in :term:`conflict resolution`, and they can provide +some number of :term:`introspectable` objects. + +.. index:: + single: add_directive + pair: configurator; adding directives + +.. _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. +Using :meth:`~pyramid.config.Configurator.add_directive` 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 added directive 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) + +Using ``config.action`` in a Directive +-------------------------------------- + +If a custom directive can't do its work exclusively in terms of existing +configurator methods (such as +:meth:`pyramid.config.Configurator.add_subscriber`, as above), the directive +may need to make use of the :meth:`pyramid.config.Configurator.action` +method. This method adds an entry to the list of "actions" that Pyramid will +attempt to process when :meth:`pyramid.config.Configurator.commit` is called. +An action is simply a dictionary that includes a :term:`discriminator`, +possibly a callback function, and possibly other metadata used by Pyramid's +action system. + +Here's an example directive which uses the "action" method: + +.. code-block:: python + :linenos: + + def add_jammyjam(config, jammyjam): + def register(): + config.registry.jammyjam = jammyjam + config.action('jammyjam', register) + + if __name__ == '__main__': + config = Configurator() + config.add_directive('add_jammyjam', add_jammyjam) + +Fancy, but what does it do? The action method accepts a number of arguments. +In the above directive named ``add_jammyjam``, we call +:meth:`~pyramid.config.Configurator.action` with two arguments: the string +``jammyjam`` is passed as the first argument named ``discriminator``, and the +closure function named ``register`` is passed as the second argument named +``callable``. + +When the :meth:`~pyramid.config.Configurator.action` method is called, it +appends an action to the list of pending configuration actions. All pending +actions with the same discriminator value are potentially in conflict with +one another (see :ref:`conflict_detection`). When the +:meth:`~pyramid.config.Configurator.commit` method of the Configurator is +called (either explicitly or as the result of calling +:meth:`~pyramid.config.Configurator.make_wsgi_app`), conflicting actions are +potentially automatically resolved as per +:ref:`automatic_conflict_resolution`. If a conflict cannot be automatically +resolved, a :exc:`ConfigurationConflictError` is raised and application +startup is prevented. + +In our above example, therefore, if a consumer of our ``add_jammyjam`` +directive did this: + +.. code-block:: python + + config.add_jammyjam('first') + config.add_jammyjam('second') + +When the action list was committed resulting from the set of calls above, our +user's application would not start, because the discriminators of the actions +generated by the two calls are in direct conflict. Automatic conflict +resolution cannot resolve the conflict (because no ``config.include`` is +involved), and the user provided no intermediate +:meth:`pyramid.config.Configurator.commit` call between the calls to +``add_jammyjam`` to ensure that the successive calls did not conflict with +each other. + +This demonstrates the purpose of the discriminator argument to the action +method: it's used to indicate a uniqueness constraint for an action. Two +actions with the same discriminator will conflict unless the conflict is +automatically or manually resolved. A discriminator can be any hashable +object, but it is generally a string or a tuple. *You use a discriminator to +declaratively ensure that the user doesn't provide ambiguous configuration +statements.* + +But let's imagine that a consumer of ``add_jammyjam`` used it in such a way +that no configuration conflicts are generated. + +.. code-block:: python + + config.add_jammyjam('first') + +What happens now? When the ``add_jammyjam`` method is called, an action is +appended to the pending actions list. When the pending configuration actions +are processed during :meth:`~pyramid.config.Configurator.commit`, and no +conflicts occur, the *callable* provided as the second argument to the +:meth:`~pyramid.config.Configurator.action` method within ``add_jammyjam`` is +called with no arguments. The callable in ``add_jammyjam`` is the +``register`` closure function. It simply sets the value +``config.registry.jammyjam`` to whatever the user passed in as the +``jammyjam`` argument to the ``add_jammyjam`` function. Therefore, the +result of the user's call to our directive will set the ``jammyjam`` +attribute of the registry to the string ``first``. *A callable is used by a +directive to defer the result of a user's call to the directive until +conflict detection has had a chance to do its job*. + +Other arguments exist to the :meth:`~pyramid.config.Configurator.action` +method, including ``args``, ``kw``, ``order``, and ``introspectables``. + +``args`` and ``kw`` exist as values, which, if passed, will be used as +arguments to the ``callable`` function when it is called back. For example +our directive might use them like so: + +.. code-block:: python + :linenos: + + def add_jammyjam(config, jammyjam): + def register(*arg, **kw): + config.registry.jammyjam_args = arg + config.registry.jammyjam_kw = kw + config.registry.jammyjam = jammyjam + config.action('jammyjam', register, args=('one',), kw={'two':'two'}) + +In the above example, when this directive is used to generate an action, and +that action is committed, ``config.registry.jammyjam_args`` will be set to +``('one',)`` and ``config.registry.jammyjam_kw`` will be set to +``{'two':'two'}``. ``args`` and ``kw`` are honestly not very useful when +your ``callable`` is a closure function, because you already usually have +access to every local in the directive without needing them to be passed +back. They can be useful, however, if you don't use a closure as a callable. + +``order`` is a crude order control mechanism. ``order`` defaults to the +integer ``0``; it can be set to any other integer. All actions that share an +order will be called before other actions that share a higher order. This +makes it possible to write a directive with callable logic that relies on the +execution of the callable of another directive being done first. For +example, Pyramid's :meth:`pyramid.config.Configurator.add_view` directive +registers an action with a higher order than the +:meth:`pyramid.config.Configurator.add_route` method. Due to this, the +``add_view`` method's callable can assume that, if a ``route_name`` was +passed to it, that a route by this name was already registered by +``add_route``, and if such a route has not already been registered, it's a +configuration error (a view that names a nonexistent route via its +``route_name`` parameter will never be called). + +``introspectables`` is a sequence of :term:`introspectable` objects. You can +pass a sequence of introspectables to the +:meth:`~pyramid.config.Configurator.action` method, which allows you to +augment Pyramid's configuration introspection system. + +.. _introspection: + +Adding Configuration Introspection +---------------------------------- + +.. note:: + + The introspection subsystem is new in Pyramid 1.3. + +Pyramid provides a configuration introspection system that can be used by +debugging tools to provide visibility into the configuration of a running +application. + +All built-in Pyramid directives (such as +:meth:`pyramid.config.Configurator.add_view` and +:meth:`pyramid.config.Configurator.add_route`) register a set of +introspectables when called. For example, when you register a view via +``add_view``, the directive registers at least one introspectable: an +introspectable about the view registration itself, providing human-consumable +values for the arguments it was passed. You can later use the introspection +query system to determine whether a particular view uses a renderer, or +whether a particular view is limited to a particular request method, or which +routes a particular view is registered against. The Pyramid "debug toolbar" +makes use of the introspection system in various ways to display information +to Pyramid developers. + +Introspection values are set when a sequence of :term:`introspectable` +objects is passed to the :meth:`~pyramid.config.Configurator.action` method. +Here's an example of a directive which uses introspectables: + +.. code-block:: python + :linenos: + + def add_jammyjam(config, value): + def register(): + config.registry.jammyjam = value + intr = config.introspectable(category_name='jammyjams', + discriminator='jammyjam', + title='a jammyjam', + type_name=None) + intr['value'] = value + config.action('jammyjam', register, introspectables=(intr,)) + + if __name__ == '__main__': + config = Configurator() + config.add_directive('add_jammyjam', add_jammyjam) + +If you notice, the above directive uses the ``introspectable`` attribute of a +Configurator (:attr:`pyramid.config.Configurator.introspectable`) to create +an introspectable object. The introspectable object's constructor requires +at least four arguments: the ``category_name``, the ``discriminator``, the +``title``, and the ``type_name``. + +The ``category_name`` is a string representing the logical category for this +introspectable. Usually the category_name is a pluralization of the type of +object being added via the action. + +The ``discriminator`` is a value unique **within the category** (unlike the +action discriminator, which must be unique within the entire set of actions). +It is typically a string or tuple representing the values unique to this +introspectable within the category. It is used to generate links and as part +of a relationship-forming target for other introspectables. + +The ``title`` is a human-consumable string that can be used by introspection +system frontends to show a friendly summary of this introspectable. + +The ``type_name`` is a value that can be used to subtype this introspectable +within its category for for sorting and presentation purposes. It can be any +value. + +An introspectable is also dictionary-like. It can contain any set of +key/value pairs, typically related to the arguments passed to its related +directive. While the category_name, discriminator, title and type_name are +*metadata* about the introspectable, the values provided as key/value pairs +are the actual data provided by the introspectable. In the above example, we +set the ``value`` key to the value of the ``value`` argument passed to the +directive. + +Our directive above mutates the introspectable, and passes it in to the +``action`` method as the first element of a tuple as the value of the +``introspectable`` keyword argument. This associates this introspectable +with the action. Introspection tools will then display this introspectable +in their index. + +Introspectable Relationships +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Two introspectables may have relationships between each other. + +.. code-block:: python + :linenos: + + def add_jammyjam(config, value, template): + def register(): + config.registry.jammyjam = (value, template) + intr = config.introspectable(category_name='jammyjams', + discriminator='jammyjam', + title='a jammyjam', + type_name=None) + intr['value'] = value + tmpl_intr = config.introspectable(category_name='jammyjam templates', + discriminator=template, + title=template, + type_name=None) + tmpl_intr['value'] = template + intr.relate('jammyjam templates', template) + config.action('jammyjam', register, introspectables=(intr, tmpl_intr)) + + if __name__ == '__main__': + config = Configurator() + config.add_directive('add_jammyjam', add_jammyjam) + +In the above example, the ``add_jammyjam`` directive registers two +introspectables. The first is related to the ``value`` passed to the +directive; the second is related to the ``template`` passed to the directive. +If you believe a concept within a directive is important enough to have its +own introspectable, you can cause the same directive to register more than +one introspectable, registering one introspectable for the "main idea" and +another for a related concept. + +The call to ``intr.relate`` above +(:meth:`pyramid.interfaces.IIntrospectable.relate`) is passed two arguments: +a category name and a directive. The example above effectively indicates +that the directive wishes to form a relationship between the ``intr`` +introspectable and the ``tmpl_intr`` introspectable; the arguments passed to +``relate`` are the category name and discriminator of the ``tmpl_intr`` +introspectable. + +Relationships need not be made between two introspectables created by the +same directive. Instead, a relationship can be formed between an +introspectable created in one directive and another introspectable created in +another by calling ``relate`` on either side with the other directive's +category name and discriminator. An error will be raised at configuration +commit time if you attempt to relate an introspectable with another +nonexistent introspectable, however. + +Introspectable relationships will show up in frontend system renderings of +introspection values. For example, if a view registration names a route +name, the introspectable related to the view callable will show a reference +to the route to which it relates to and vice versa. diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index 9c96248f2..c464203f0 100644 --- a/docs/narr/extending.rst +++ b/docs/narr/extending.rst @@ -200,8 +200,8 @@ like this: 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``). + application (e.g. ``$myvenv/bin/python setup.py develop`` or + ``$myvenv/bin/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 diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index 45d65402c..c082f616b 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -71,11 +71,10 @@ named ``hello_world``. :linenos: :pyobject: hello_world -This function doesn't do anything very difficult. The functions accepts a -single argument (``request``). The ``hello_world`` function returns an -instance of the :class:`pyramid.response.Response`. The single argument to -the class' constructor is value computed from arguments matched from the url -route. This value becomes the body of the response. +The function accepts a single argument (``request``) and it returns an +instance of the :class:`pyramid.response.Response` class. The single +argument to the class' constructor is a string computed from parameters +matched from the URL. This value becomes the body of the response. This function is known as a :term:`view callable`. A view callable accepts a single argument, ``request``. It is expected to return a @@ -134,7 +133,7 @@ Using the ``if`` clause is necessary -- or at least best practice -- because code in a Python ``.py`` file may be eventually imported via the Python ``import`` statement by another ``.py`` file. ``.py`` files that are imported by other ``.py`` files are referred to as *modules*. By using the -``if __name__ == 'main':`` idiom, the script above is indicating that it does +``if __name__ == '__main__':`` idiom, the script above is indicating that it does not want the code within the ``if`` statement to execute if this module is imported from another; the code within the ``if`` block should only be run during a direct script execution. @@ -157,7 +156,7 @@ Adding Configuration :lines: 10-11 First line above calls the :meth:`pyramid.config.Configurator.add_route` -method, which registers a :term:`route` to match any url path that begins +method, which registers a :term:`route` to match any URL path that begins with ``/hello/`` followed by a string. The second line, ``config.add_view(hello_world, route_name='hello')``, @@ -208,7 +207,7 @@ WSGI Application Serving Finally, we actually serve the application to requestors by starting up a WSGI server. We happen to use the :func:`paste.httpserver.serve` WSGI server runner, passing it the ``app`` object (a :term:`router`) as the application -we wish to serve. We also pass in an argument ``host=='0.0.0.0'``, meaning +we wish to serve. We also pass in an argument ``host='0.0.0.0'``, meaning "listen on all TCP interfaces." By default, the HTTP server listens only on the ``127.0.0.1`` interface, which is problematic if you're running the server on a remote system and you wish to access it with a web browser diff --git a/docs/narr/helloworld.py b/docs/narr/helloworld.py index 93a403a13..7c26c8cdc 100644 --- a/docs/narr/helloworld.py +++ b/docs/narr/helloworld.py @@ -10,6 +10,6 @@ if __name__ == '__main__': config.add_route('hello', '/hello/{name}') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() - server = make_server('0.0.0.0', 8080) + server = make_server('0.0.0.0', 8080, app) server.serve_forever() diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index c2ecba9bb..e261f9a11 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -149,7 +149,7 @@ generated by using it. For example: from pyramid.i18n import TranslationStringFactory _ = TranslationStringFactory('pyramid') - ts = _('Add ${number}', msgid='add-number', mapping={'number':1}) + ts = _('add-number', default='Add ${number}', mapping={'number':1}) .. note:: We assigned the translation string factory to the name ``_``. This is a convention which will be supported by translation @@ -170,7 +170,7 @@ to: :linenos: from pyramid.i18n import TranslationString as _ - ts = _('Add ${number}', msgid='add-number', mapping={'number':1}, + ts = _('add-number', default='Add ${number}', mapping={'number':1}, domain='pyramid') You can set up your own translation string factory much like the one @@ -185,7 +185,7 @@ do something like this: from pyramid.i18n import TranslationStringFactory _ = TranslationStringFactory('form') - ts = _('Add ${number}', msgid='add-number', mapping={'number':1}) + ts = _('add-number', default='Add ${number}', mapping={'number':1}) Creating a unique domain for your application via a translation string factory is best practice. Using your own unique translation domain @@ -249,7 +249,7 @@ GNU gettext uses three types of files in the translation framework, The tools for working with :term:`gettext` translation files related to a :app:`Pyramid` application is :term:`Babel` and :term:`Lingua`. Lingua is a -Balel extension that provides support for scraping i18n references out of +Babel extension that provides support for scraping i18n references out of Python and Chameleon files. .. index:: @@ -347,7 +347,7 @@ extract the messages: $ cd /place/where/myapplication/setup.py/lives $ mkdir -p myapplication/locale - $ python setup.py extract_messages + $ $myvenv/bin/python setup.py extract_messages The message catalog ``.pot`` template will end up in: @@ -439,7 +439,7 @@ init_catalog`` command: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives - $ python setup.py init_catalog -l es + $ $myvenv/bin/python setup.py init_catalog -l es By default, the message catalog ``.po`` file will end up in: @@ -471,7 +471,7 @@ Then use the ``setup.py update_catalog`` command. .. code-block:: text $ cd /place/where/myapplication/setup.py/lives - $ python setup.py update_catalog + $ $myvenv/bin/python setup.py update_catalog .. index:: pair: compiling; message catalog @@ -487,7 +487,7 @@ translations, compile ``.po`` files to ``.mo`` files: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives - $ python setup.py compile_catalog + $ $myvenv/bin/python setup.py compile_catalog This will create a ``.mo`` file for each ``.po`` file in your application. As long as the :term:`translation directory` in which diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 66bcea706..3de4d6e27 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -9,19 +9,18 @@ Installing :app:`Pyramid` Before You Install ------------------ -You will need `Python <http://python.org>`_ version 2.5 or better to +You will need `Python <http://python.org>`_ version 2.6 or better to run :app:`Pyramid`. .. sidebar:: Python Versions - As of this writing, :app:`Pyramid` has been tested under Python 2.5.5, - Python 2.6.6, and Python 2.7.2. :app:`Pyramid` does not run under any - version of Python before 2.5, and does not yet run under Python 3.X. + As of this writing, :app:`Pyramid` has been tested under Python 2.6.6, + Python 2.7.2, and Python 3.2. :app:`Pyramid` does not run under any + version of Python before 2.6. :app:`Pyramid` is known to run on all popular UNIX-like systems such as Linux, MacOS X, and FreeBSD as well as on Windows platforms. It is also -known to run on Google's App Engine, :term:`PyPy` (1.5 and 1.6), and -:term:`Jython` (2.5.2). +known to run on Google's App Engine, and :term:`PyPy` (1.6+). :app:`Pyramid` installation does not require the compilation of any C code, so you need only a Python interpreter that meets the @@ -325,25 +324,6 @@ Installing :app:`Pyramid` on Google App Engine :ref:`appengine_tutorial` documents the steps required to install a :app:`Pyramid` application on Google App Engine. -.. index:: - single: installing on Jython - -Installing :app:`Pyramid` on Jython --------------------------------------- - -:app:`Pyramid` is known to work under :term:`Jython` version 2.5.1. -Install :term:`Jython`, and then follow the installation steps for -:app:`Pyramid` on your platform described in one of the sections -entitled :ref:`installing_unix` or :ref:`installing_windows` above, -replacing the ``python`` command with ``jython`` as necessary. The -steps are exactly the same except you should use the ``jython`` -command name instead of the ``python`` command name. - -One caveat exists to using :app:`Pyramid` under Jython: the :term:`Chameleon` -templating engine does not work on Jython. However, the :term:`Mako` -templating system, which is also included with Pyramid, does work under -Jython; use it instead. - What Gets Installed ------------------- diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 547f88ef3..7c6ad00f3 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -219,7 +219,7 @@ that the Pyramid core doesn't. Add-on packages already exist which let you easily send email, let you use the Jinja2 templating system, let you use XML-RPC or JSON-RPC, let you integrate with jQuery Mobile, etc. -Examples: https://docs.pylonsproject.org/docs/pyramid.html#pyramid-add-on-documentation +Examples: http://docs.pylonsproject.org/docs/pyramid.html#pyramid-add-on-documentation Class-based and function-based views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -808,11 +808,11 @@ Every release of Pyramid has 100% statement coverage via unit and integration tests, as measured by the ``coverage`` tool available on PyPI. It also has greater than 95% decision/condition coverage as measured by the ``instrumental`` tool available on PyPI. It is automatically tested by the -Jenkins tool on Python 2.5, Python 2.6, Python 2.7, Jython and PyPy after -each commit to its GitHub repository. Official Pyramid add-ons are held to a -similar testing standard. We still find bugs in Pyramid and its official -add-ons, but we've noticed we find a lot more of them while working on other -projects that don't have a good testing regime. +Jenkins tool on Python 2.6, Python 2.7, Python 3.2 and PyPy after each commit +to its GitHub repository. Official Pyramid add-ons are held to a similar +testing standard. We still find bugs in Pyramid and its official add-ons, +but we've noticed we find a lot more of them while working on other projects +that don't have a good testing regime. Example: http://jenkins.pylonsproject.org/ @@ -842,7 +842,7 @@ official narrative docs. In any case, the Pyramid documentation is comprehensive. Example: The rest of this documentation and the cookbook at -https://docs.pylonsproject.org/projects/pyramid_cookbook/dev/ . +http://docs.pylonsproject.org/projects/pyramid_cookbook/dev/ . .. index:: single: Pylons Project diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst new file mode 100644 index 000000000..11d779854 --- /dev/null +++ b/docs/narr/introspector.rst @@ -0,0 +1,557 @@ +.. index:: + single: introspection + single: introspector + +.. _using_introspection: + +Pyramid Configuration Introspection +=================================== + +When Pyramid starts up, each call to a :term:`configuration directive` causes +one or more :term:`introspectable` objects to be registered with an +:term:`introspector`. The introspector can be queried by application code to +obtain information about the configuration of the running application. This +feature is useful for debug toolbars, command-line scripts which show some +aspect of configuration, and for runtime reporting of startup-time +configuration settings. + +.. warning:: + + Introspection is new in Pyramid 1.3. + +Using the Introspector +---------------------- + +Here's an example of using Pyramid's introspector from within a view +callable: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(route_name='bar') + def route_accepts(request): + introspector = request.registry.introspector + route_name = request.matched_route.name + route_intr = introspector.get('routes', route_name) + return Response(str(route_intr['pattern'])) + +This view will return a response that contains the "pattern" argument +provided to the ``add_route`` method of the route which matched when the view +was called. It uses the :meth:`pyramid.interfaces.IIntrospector.get` method +to return an introspectable in the category ``routes`` with a +:term:`discriminator` equal to the matched route name. It then uses the +returned introspectable to obtain a "pattern" value. + +The introspectable returned by the query methods of the introspector has +methods and attributes described by +:class:`pyramid.interfaces.IIntrospectable`. In particular, the +:meth:`~pyramid.interfaces.IIntrospector.get`, +:meth:`~pyramid.interfaces.IIntrospector.get_category`, +:meth:`~pyramid.interfaces.IIntrospector.categories`, +:meth:`~pyramid.interfaces.IIntrospector.categorized`, and +:meth:`~pyramid.interfaces.IIntrospector.related` methods of an introspector +can be used to query for introspectables. + +Introspectable Objects +---------------------- + +Introspectable objects are returned from query methods of an introspector. +Each introspectable object implements the attributes and methods +documented at :class:`pyramid.interfaces.IIntrospectable`. + +The important attributes shared by all introspectables are the following: + +``title`` + + A human-readable text title describing the introspectable + +``category_name`` + + A text category name describing the introspection category to which this + introspectable belongs. It is often a plural if there are expected to be + more than one introspectable registered within the category. + +``discriminator`` + + A hashable object representing the unique value of this introspectable + within its category. + +``discriminator_hash`` + + The integer hash of the discriminator (useful for using in HTML links). + +``type_name`` + + The text name of a subtype within this introspectable's category. If there + is only one type name in this introspectable's category, this value will + often be a singular version of the category name but it can be an arbitrary + value. + +``action_info`` + + An object describing the directive call site which caused this + introspectable to be registered; contains attributes described in + :class:`pyramid.interfaces.IActionInfo`. + +Besides having the attributes described above, an introspectable is a +dictionary-like object. An introspectable can be queried for data values via +its ``__getitem__``, ``get``, ``keys``, ``values``, or ``items`` methods. +For example: + +.. code-block:: python + :linenos: + + route_intr = introspector.get('routes', 'edit_user') + pattern = route_intr['pattern'] + +Pyramid Introspection Categories +-------------------------------- + +The list of concrete introspection categories provided by built-in Pyramid +configuration directives follows. Add-on packages may supply other +introspectables in categories not described here. + +``subscribers`` + + Each introspectable in the ``subscribers`` category represents a call to + :meth:`pyramid.config.Configurator.add_subscriber` (or the decorator + equivalent); each will have the following data. + + ``subscriber`` + + The subscriber callable object (the resolution of the ``subscriber`` + argument passed to ``add_susbcriber``). + + ``interfaces`` + + A sequence of interfaces (or classes) that are subscribed to (the + resolution of the ``ifaces`` argument passed to ``add_subscriber``). + +``response adapters`` + + Each introspectable in the ``response adapters`` category represents a call + to :meth:`pyramid.config.Configurator.add_response_adapter` (or a decorator + equivalent); each will have the following data. + + ``adapter`` + + The adapter object (the resolved ``adapter`` argument to + ``add_response_adapter``). + + ``type`` + + The resolved ``type_or_iface`` argument passed to + ``add_response_adapter``. + +``root factories`` + + Each introspectable in the ``root factories`` category represents a call to + :meth:`pyramid.config.Configurator.set_root_factory` (or the Configurator + constructor equivalent) *or* a ``factory`` argument passed to + :meth:`pyramid.config.Configurator.add_route`; each will have the following + data. + + ``factory`` + + The factory object (the resolved ``factory`` argument to + ``set_root_factory``). + + ``route_name`` + + The name of the route which will use this factory. If this is the + *default* root factory (if it's registered during a call to + ``set_root_factory``), this value will be ``None``. + +``session factory`` + + Only one introspectable will exist in the ``session factory`` category. It + represents a call to :meth:`pyramid.config.Configurator.set_session_factory` + (or the Configurator constructor equivalent); it will have the following + data. + + ``factory`` + + The factory object (the resolved ``factory`` argument to + ``set_session_factory``). + +``request factory`` + + Only one introspectable will exist in the ``request factory`` category. It + represents a call to :meth:`pyramid.config.Configurator.set_request_factory` + (or the Configurator constructor equivalent); it will have the following + data. + + ``factory`` + + The factory object (the resolved ``factory`` argument to + ``set_request_factory``). + +``locale negotiator`` + + Only one introspectable will exist in the ``locale negotiator`` category. + It represents a call to + :meth:`pyramid.config.Configurator.set_locale_negotiator` (or the + Configurator constructor equivalent); it will have the following data. + + ``negotiator`` + + The factory object (the resolved ``negotiator`` argument to + ``set_locale_negotiator``). + +``renderer factories`` + + Each introspectable in the ``renderer factories`` category represents a + call to :meth:`pyramid.config.Configurator.add_renderer` (or the + Configurator constructor equivalent); each will have the following data. + + ``name`` + + The name of the renderer (the value of the ``name`` argument to + ``add_renderer``). + + ``factory`` + + The factory object (the resolved ``factory`` argument to + ``add_renderer``). + +``renderer globals factory`` + + There will be one and only one introspectable in the ``renderer globals + factory`` category. It represents a call to + :meth:`pyramid.config.Configurator.set_renderer_globals_factory`; it will + have the following data. + + ``factory`` + + The factory object (the resolved ``factory`` argument to + ``set_renderer_globals_factory``). + +``routes`` + + Each introspectable in the ``routes`` category represents a call to + :meth:`pyramid.config.Configurator.add_route`; each will have the following + data. + + ``name`` + + The ``name`` argument passed to ``add_route``. + + ``pattern`` + + The ``pattern`` argument passed to ``add_route``. + + ``factory`` + + The (resolved) ``factory`` argument passed to ``add_route``. + + ``xhr`` + + The ``xhr`` argument passed to ``add_route``. + + ``request_method`` + + The ``request_method`` argument passed to ``add_route``. + + ``request_methods`` + + A sequence of request method names implied by the ``request_method`` + argument passed to ``add_route`` or the value ``None`` if a + ``request_method`` argument was not supplied. + + ``path_info`` + + The ``path_info`` argument passed to ``add_route``. + + ``request_param`` + + The ``request_param`` argument passed to ``add_route``. + + ``header`` + + The ``header`` argument passed to ``add_route``. + + ``accept`` + + The ``accept`` argument passed to ``add_route``. + + ``traverse`` + + The ``traverse`` argument passed to ``add_route``. + + ``custom_predicates`` + + The ``custom_predicates`` argument passed to ``add_route``. + + ``pregenerator`` + + The ``pregenerator`` argument passed to ``add_route``. + + ``static`` + + The ``static`` argument passed to ``add_route``. + + ``use_global_views`` + + The ``use_global_views`` argument passed to ``add_route``. + + ``object`` + + The :class:`pyramid.interfaces.IRoute` object that is used to perform + matching and generation for this route. + +``authentication policy`` + + There will be one and only one introspectable in the ``authentication + policy`` category. It represents a call to the + :meth:`pyramid.config.Configurator.set_authentication_policy` method (or + its Configurator constructor equivalent); it will have the following data. + + ``policy`` + + The policy object (the resolved ``policy`` argument to + ``set_authentication_policy``). + +``authorization policy`` + + There will be one and only one introspectable in the ``authorization + policy`` category. It represents a call to the + :meth:`pyramid.config.Configurator.set_authorization_policy` method (or its + Configurator constructor equivalent); it will have the following data. + + ``policy`` + + The policy object (the resolved ``policy`` argument to + ``set_authorization_policy``). + +``default permission`` + + There will be one and only one introspectable in the ``default permission`` + category. It represents a call to the + :meth:`pyramid.config.Configurator.set_default_permission` method (or its + Configurator constructor equivalent); it will have the following data. + + ``value`` + + The permission name passed to ``set_default_permission``. + +``views`` + + Each introspectable in the ``views`` category represents a call to + :meth:`pyramid.config.Configurator.add_view`; each will have the following + data. + + ``name`` + + The ``name`` argument passed to ``add_view``. + + ``context`` + + The (resolved) ``context`` argument passed to ``add_view``. + + ``containment`` + + The (resolved) ``containment`` argument passed to ``add_view``. + + ``request_param`` + + The ``request_param`` argument passed to ``add_view``. + + ``request_methods`` + + A sequence of request method names implied by the ``request_method`` + argument passed to ``add_view`` or the value ``None`` if a + ``request_method`` argument was not supplied. + + ``route_name`` + + The ``route_name`` argument passed to ``add_view``. + + ``attr`` + + The ``attr`` argument passed to ``add_view``. + + ``xhr`` + + The ``xhr`` argument passed to ``add_view``. + + ``accept`` + + The ``accept`` argument passed to ``add_view``. + + ``header`` + + The ``header`` argument passed to ``add_view``. + + ``path_info`` + + The ``path_info`` argument passed to ``add_view``. + + ``match_param`` + + The ``match_param`` argument passed to ``add_view``. + + ``callable`` + + The (resolved) ``view`` argument passed to ``add_view``. Represents the + "raw" view callable. + + ``derived_callable`` + + The view callable derived from the ``view`` argument passed to + ``add_view``. Represents the view callable which Pyramid itself calls + (wrapped in security and other wrappers). + + ``mapper`` + + The (resolved) ``mapper`` argument passed to ``add_view``. + + ``decorator`` + + The (resolved) ``decorator`` argument passed to ``add_view``. + +``permissions`` + + Each introspectable in the ``permissions`` category represents a call to + :meth:`pyramid.config.Configurator.add_view` that has an explicit + ``permission`` argument to *or* a call to + :meth:`pyramid.config.Configurator.set_default_permission`; each will have + the following data. + + ``value`` + + The permission name passed to ``add_view`` or ``set_default_permission``. + +``templates`` + + Each introspectable in the ``templates`` category represents a call to + :meth:`pyramid.config.Configurator.add_view` that has a ``renderer`` + argument which points to a template; each will have the following data. + + ``name`` + + The renderer's name (a string). + + ``type`` + + The renderer's type (a string). + + ``renderer`` + + The :class:`pyramid.interfaces.IRendererInfo` object which represents + this template's renderer. + +``view mapper`` + + Each introspectable in the ``permissions`` category represents a call to + :meth:`pyramid.config.Configurator.add_view` that has an explicit + ``mapper`` argument to *or* a call to + :meth:`pyramid.config.Configurator.set_view_mapper`; each will have + the following data. + + ``mapper`` + + The (resolved) ``mapper`` argument passed to ``add_view`` or + ``set_view_mapper``. + +``asset overrides`` + + Each introspectable in the ``asset overrides`` category represents a call + to :meth:`pyramid.config.Configurator.override_asset`; each will have the + following data. + + ``to_override`` + + The ``to_override`` argument (an asset spec) passed to + ``override_asset``. + + ``override_with`` + + The ``override_with`` argument (an asset spec) passed to + ``override_asset``. + +``translation directories`` + + Each introspectable in the ``asset overrides`` category represents an + individual element in a ``specs`` argument passed to to + :meth:`pyramid.config.Configurator.add_translation_dirs`; each will have + the following data. + + ``directory`` + + The absolute path of the translation directory. + + ``spec`` + + The asset specification passed to ``add_translation_dirs``. + +``tweens`` + + Each introspectable in the ``tweens`` category represents a call to + :meth:`pyramid.config.Configurator.add_tween`; each will have the following + data. + + ``name`` + + The dotted name to the tween factory as a string (passed as + the ``tween_factory`` argument to ``add_tween``). + + ``factory`` + + The (resolved) tween factory object. + + ``type`` + + ``implict`` or ``explicit`` as a string. + + ``under`` + + The ``under`` argument passed to ``add_tween`` (a string). + + ``over`` + + The ``over`` argument passed to ``add_tween`` (a string). + +``static views`` + + Each introspectable in the ``static views`` category represents a call to + :meth:`pyramid.config.Configurator.add_static_view`; each will have the + following data. + + ``name`` + + The ``name`` argument provided to ``add_static_view``. + + ``spec`` + + A normalized version of the ``spec`` argument provided to + ``add_static_view``. + +Introspection in the Toolbar +---------------------------- + +The Pyramid debug toolbar (part of the ``pyramid_debugtoolbar`` package) +provides a canned view of all registered introspectables and their +relationships. It looks something like this: + +.. image:: tb_introspector.png + +Disabling Introspection +----------------------- + +You can disable Pyramid introspection by passing the object +:attr:`pyramid.registry.noop_introspector` to the :term:`Configurator` +constructor in your application setup: + +.. code-block:: python + + from pyramid.config import Configurator + from pyramid.registry import noop_introspector + config = Configurator(..., introspector=noop_introspector) + +When the noop introspector is active, all introspectables generated by +configuration directives are thrown away. A noop introspector behaves just +like a "real" introspector, but the methods of a noop introspector do nothing +and return null values. diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index 5377d0c66..044655c1f 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -16,6 +16,8 @@ how to send log messages to loggers that you've configured. a third-party scaffold which does not create these files, the configuration information in this chapter will not be applicable. +.. _logging_config: + Logging Configuration --------------------- @@ -291,7 +293,7 @@ Logging Exceptions To log (or email) exceptions generated by your :app:`Pyramid` application, use the :term:`pyramid_exclog` package. Details about its configuration are in its `documentation -<https://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_. +<http://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_. Request Logging with Paste's TransLogger ---------------------------------------- diff --git a/docs/narr/muchadoabouttraversal.rst b/docs/narr/muchadoabouttraversal.rst index a948e57cc..4a249ed0d 100644 --- a/docs/narr/muchadoabouttraversal.rst +++ b/docs/narr/muchadoabouttraversal.rst @@ -4,7 +4,9 @@ Much Ado About Traversal ======================== -.. note:: This chapter was adapted, with permission, from a blog post by `Rob +.. 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/ . diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 4f96448af..af8714573 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -28,7 +28,6 @@ as part of Pyramid. single: starter scaffold single: zodb scaffold single: alchemy scaffold - single: routesalchemy scaffold .. _additional_paster_scaffolds: @@ -47,27 +46,17 @@ each other on a number of axes: The included scaffolds are these: ``starter`` - URL mapping via :term:`traversal` and no persistence mechanism. + URL mapping via :term:`URL dispatch` and no persistence mechanism. ``zodb`` - URL mapping via :term:`traversal` and persistence via :term:`ZODB`. - -``routesalchemy`` - URL mapping via :term:`URL dispatch` and persistence via - :term:`SQLAlchemy` + URL mapping via :term:`traversal` and persistence via :term:`ZODB`. *Note + that, as of this writing, this scaffold will not run under Python 3, only + under Python 2.* ``alchemy`` - URL mapping via :term:`traversal` and persistence via + URL mapping via :term:`URL dispatch` and persistence via :term:`SQLAlchemy` -.. note:: - -Rather than use any of the above scaffolds, Pylons 1 users may feel more -comfortable installing the :term:`Akhet` development environment, which -provides a scaffold named ``akhet``. This scaffold configures a Pyramid -application in a "Pylons-esque" way, including the use of a :term:`view -handler` to map URLs to code (a handler is much like a Pylons "controller"). - .. index:: single: creating a project single: project @@ -98,18 +87,18 @@ Or on Windows: The above command uses the ``pcreate`` command to create a project with the ``starter`` scaffold. To use a different scaffold, such as -``routesalchemy``, you'd just change the ``-s`` argument value. For example, +``alchemy``, you'd just change the ``-s`` argument value. For example, on UNIX: .. code-block:: text - $ bin/pcreate -s routesalchemy MyProject + $ bin/pcreate -s alchemy MyProject Or on Windows: .. code-block:: text - $ Scripts\pcreate routesalchemy MyProject + $ Scripts\pcreate alchemy MyProject Here's sample output from a run of ``pcreate`` on UNIX for a project we name ``MyProject``: @@ -386,7 +375,6 @@ structure: |-- MANIFEST.in |-- myproject | |-- __init__.py - | |-- resources.py | |-- static | | |-- favicon.ico | | |-- logo.png @@ -686,8 +674,6 @@ The ``myproject`` :term:`package` lives inside the ``MyProject`` ``main`` function which is used as a entry point for commands such as ``pserve``, ``pshell``, ``pviews``, and others. -#. A ``resources.py`` module, which contains :term:`resource` code. - #. A ``templates`` directory, which contains :term:`Chameleon` (or other types of) templates. @@ -723,34 +709,25 @@ also informs Python that the directory which contains it is a *package*. #. Line 1 imports the :term:`Configurator` class from :mod:`pyramid.config` that we use later. -#. Line 2 imports the ``Root`` class from :mod:`myproject.resources` that we - use later. - -#. Lines 4-12 define a function named ``main`` that returns a :app:`Pyramid` +#. Lines 3-16 define a function named ``main`` that returns a :app:`Pyramid` WSGI application. This function is meant to be called by the :term:`PasteDeploy` framework as a result of running ``pserve``. Within this function, application configuration is performed. - Lines 8-10 register a "default view" (a view that has no ``name`` - attribute). It is registered so that it will be found when the - :term:`context` of the request is an instance of the - :class:`myproject.resources.Root` class. The first argument to - ``add_view`` points at a Python function that does all the work for this - view, also known as a :term:`view callable`, via a :term:`dotted Python - name`. The view declaration also names a ``renderer``, which in this case - is a template that will be used to render the result of the view callable. - This particular view declaration points at - ``myproject:templates/mytemplate.pt``, which is a :term:`asset - specification` that specifies the ``mytemplate.pt`` file within the - ``templates`` directory of the ``myproject`` package. The template file - it actually points to is a :term:`Chameleon` ZPT template file. - - Line 11 registers a static view, which will serve up the files from the + Line 6 creates an instance of a :term:`Configurator`. + + Line 7 registers a static view, which will serve up the files from the ``mypackage:static`` :term:`asset specification` (the ``static`` directory of the ``mypackage`` package). - Line 12 returns a :term:`WSGI` application to the caller of the function + Line 8 adds a :term:`route` to the configuration. This route is later + used by a view in the ``views`` module. + + Line 9 calls ``config.scan()``, which picks up view registrations declared + elsewhere in the package (in this case, in the ``views.py`` module). + + Line 10 returns a :term:`WSGI` application to the caller of the function (Pyramid's pserve). .. index:: @@ -768,10 +745,22 @@ and which returns a :term:`response`. :language: python :linenos: -This bit of code was registered as the view callable within ``__init__.py`` -(via ``add_view``). ``add_view`` said that the default URL for instances -that are of the class :class:`myproject.resources.Root` should run this -:func:`myproject.views.my_view` function. +Lines 3-5 define and register a :term:`view callable` named ``my_view``. The +function named ``my_view`` is decorated with a ``view_config`` decorator +(which is processed by the ``config.scan()`` line in our ``__init__.py``). +The view_config decorator asserts that this view be found when a +:term:`route` named ``home`` is matched. In our case, because our +``__init__.py`` maps the route named ``home`` to the URL pattern ``/``, this +route will match when a visitor visits the root URL. The view_config +decorator also names a ``renderer``, which in this case is a template that +will be used to render the result of the view callable. This particular view +declaration points at ``templates/mytemplate.pt``, which is a :term:`asset +specification` that specifies the ``mytemplate.pt`` file within the +``templates`` directory of the ``myproject`` package. The asset +specification could have also been specified as +``myproject:templates/mytemplate.pt``; the leading package name and colon is +optional. The template file it actually points to is a :term:`Chameleon` ZPT +template file. This view callable function is handed a single piece of information: the :term:`request`. The *request* is an instance of the :term:`WebOb` @@ -781,8 +770,7 @@ This view returns a dictionary. When this view is invoked, a :term:`renderer` converts the dictionary returned by the view into HTML, and returns the result as the :term:`response`. This view is configured to invoke a renderer which uses a :term:`Chameleon` ZPT template -(``mypackage:templates/my_template.pt``, as specified in the ``__init__.py`` -file call to ``add_view``). +(``templates/my_template.pt``). See :ref:`views_which_use_a_renderer` for more information about how views, renderers, and templates relate and cooperate. @@ -798,35 +786,6 @@ renderers, and templates relate and cooperate. the speed at which templates may be rendered. .. index:: - single: resources.py - -.. _resourcespy_project_section: - -``resources.py`` -~~~~~~~~~~~~~~~~ - -The ``resources.py`` module provides the :term:`resource` data and behavior -for our application. Resources are objects which exist to provide site -structure in applications which use :term:`traversal` to map URLs to code. -We write a class named ``Root`` that provides the behavior for the root -resource. - -.. literalinclude:: MyProject/myproject/resources.py - :language: python - :linenos: - -#. Lines 1-3 define the Root class. The Root class is a "root resource - factory" function that will be called by the :app:`Pyramid` *Router* for - each request when it wants to find the root of the resource tree. - -In a "real" application, the Root object would likely not be such a simple -object. Instead, it might be an object that could access some persistent -data store, such as a database. :app:`Pyramid` doesn't make any assumption -about which sort of data storage you'll want to use, so the sample -application uses an instance of :class:`myproject.resources.Root` to -represent the root. - -.. index:: single: static directory ``static`` @@ -838,11 +797,11 @@ template. It includes CSS and images. ``templates/mytemplate.pt`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The single :term:`Chameleon` template that exists in the project. Its contents -are too long to show here, but it displays a default page when rendered. It -is referenced by the call to ``add_view`` as the ``renderer`` attribute in -the ``__init__`` file. See :ref:`views_which_use_a_renderer` for more -information about renderers. +The single :term:`Chameleon` template that exists in the project. Its +contents are too long to show here, but it displays a default page when +rendered. It is referenced by the call to ``@view_config`` as the +``renderer`` of the ``my_view`` view callable in the ``views.py`` file. See +:ref:`views_which_use_a_renderer` for more information about renderers. Templates are accessed and used by view configurations and sometimes by view functions themselves. See :ref:`templates_used_directly` and @@ -907,39 +866,12 @@ named ``views`` instead of within a single ``views.py`` file, you might: can be empty, this just tells Python that the ``views`` directory is a *package*. -Then change the __init__.py of your myproject project (*not* the -``__init__.py`` you just created in the ``views`` directory, the one in its -parent directory). For example, from something like: - -.. code-block:: python - :linenos: - - config.add_view('myproject.views.my_view', - renderer='myproject:templates/mytemplate.pt') - -To this: - -.. code-block:: python - :linenos: - - 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 -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: - - config.add_view('myproject.views.anothermodule.my_view', - renderer='myproject:templates/anothertemplate.pt') - -This pattern can be used to rearrage code referred to by any Pyramid API -argument which accepts a :term:`dotted Python name` or direct object -reference. +You can then continue to add view callable functions to the ``blog.py`` +module, but you can also add other ``.py`` files which contain view callable +functions to the ``views`` directory. As long as you use the +``@view_config`` directive to register views in conjuction with +``config.scan()`` they will be picked up automatically when the application +is restarted. Using the Interactive Shell --------------------------- @@ -949,16 +881,39 @@ configuration as would be loaded if you were running your Pyramid application via ``pserve``. This can be a useful debugging tool. See :ref:`interactive_shell` for more details. +.. _alternate_wsgi_server: + Using an Alternate WSGI Server ------------------------------ -The code generated by a :app:`Pyramid` scaffold assumes that you will be +The code generated by :app:`Pyramid` scaffolding assumes that you will be using the ``pserve`` command to start your application while you do -development. However, ``pserve`` is by no means the only way to start up and -serve a :app:`Pyramid` application. As we saw in :ref:`firstapp_chapter`, -``pserve`` needn't be invoked at all to run a :app:`Pyramid` application. -The use of ``pserve`` to run a :app:`Pyramid` application is purely -conventional based on the output of its scaffold. +development. The default rendering of Pyramid scaffolding uses the *wsgiref* +WSGI server, which is a server that is ill-suited for production usage: its +main feature is that it works on all platforms and all systems, making it a +good choice as a default server from the perspective of Pyramid's developers. + +To use a server more suitable for production, you have a number of choices. +Replace the ``use = egg:pyramid#wsgref`` line in your ``production.ini`` with +one of the following. + +``use = egg:Paste#http`` + + ``paste.httpserver`` is Windows, UNIX, and Python 2 compatible. You'll + need to ``easy_install Paste`` into your Pyramid virtualenv for this server + to work. + +``use = egg:pyramid#cherrypy`` + + The ``CherryPy`` WSGI server is Windows, UNIX, Python 2, and Python 3 + compatible. You'll need to ``easy_install CherryPy`` into your Pyramid + virtualenv for this server to work. + +``pserve`` is by no means the only way to start up and serve a :app:`Pyramid` +application. As we saw in :ref:`firstapp_chapter`, ``pserve`` needn't be +invoked at all to run a :app:`Pyramid` application. The use of ``pserve`` to +run a :app:`Pyramid` application is purely conventional based on the output +of its scaffold. Any :term:`WSGI` server is capable of running a :app:`Pyramid` application. Some WSGI servers don't require the :term:`PasteDeploy` framework's diff --git a/docs/narr/router.rst b/docs/narr/router.rst index d08261b17..b78362066 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -9,74 +9,67 @@ Request Processing ================== -Once a :app:`Pyramid` application is up and running, it is ready to -accept requests and return responses. +Once a :app:`Pyramid` application is up and running, it is ready to accept +requests and return responses. What happens from the time a :term:`WSGI` +request enters a :app:`Pyramid` application through to the point that +:app:`Pyramid` hands off a response back to WSGI for upstream processing? -What happens from the time a :term:`WSGI` request enters a -:app:`Pyramid` application through to the point that -:app:`Pyramid` hands off a response back to WSGI for upstream -processing? +#. A user initiates a request from his browser to the hostname and port + number of the WSGI server used by the :app:`Pyramid` application. -#. A user initiates a request from his browser to the hostname and - port number of the WSGI server used by the :app:`Pyramid` - application. - -#. The WSGI server used by the :app:`Pyramid` application passes - the WSGI environment to the ``__call__`` method of the - :app:`Pyramid` :term:`router` object. +#. The WSGI server used by the :app:`Pyramid` application passes the WSGI + environment to the ``__call__`` method of the :app:`Pyramid` + :term:`router` object. #. A :term:`request` object is created based on the WSGI environment. -#. 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 +#. 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. #. A :class:`~pyramid.events.NewRequest` :term:`event` is sent to any subscribers. -#. If any :term:`route` has been defined within application - configuration, the :app:`Pyramid` :term:`router` calls a - :term:`URL dispatch` "route mapper." The job of the mapper is to - examine the request to determine whether any user-defined - :term:`route` matches the current WSGI environment. The - :term:`router` passes the request as an argument to the mapper. - -#. If any route matches, the request is mutated; a ``matchdict`` and - ``matched_route`` attributes are added to the request object; the - former contains a dictionary representing the matched dynamic - elements of the request's ``PATH_INFO`` value, the latter contains - the :class:`~pyramid.interfaces.IRoute` object representing the - route which matched. The root object associated with the route - found is also generated: if the :term:`route configuration` which - matched has an associated a ``factory`` argument, this factory is - used to generate the root object, otherwise a default :term:`root - factory` is used. - -#. If a route match was *not* found, and a ``root_factory`` argument - was passed to the :term:`Configurator` constructor, that callable - is used to generate the root object. If the ``root_factory`` - argument passed to the Configurator constructor was ``None``, a - default root factory is used to generate a root object. - -#. The :app:`Pyramid` router calls a "traverser" function with the - root object and the request. The traverser function attempts to - traverse the root object (using any existing ``__getitem__`` on the - root object and subobjects) to find a :term:`context`. If the root - object has no ``__getitem__`` method, the root itself is assumed to - be the context. The exact traversal algorithm is described in - :ref:`traversal_chapter`. The traverser function returns a - dictionary, which contains a :term:`context` and a :term:`view - name` as well as other ancillary information. - -#. The request is decorated with various names returned from the - traverser (such as ``context``, ``view_name``, and so forth), so - they can be accessed via e.g. ``request.context`` within - :term:`view` code. - -#. A :class:`~pyramid.events.ContextFound` :term:`event` is - sent to any subscribers. +#. If any :term:`route` has been defined within application configuration, + the :app:`Pyramid` :term:`router` calls a :term:`URL dispatch` "route + mapper." The job of the mapper is to examine the request to determine + whether any user-defined :term:`route` matches the current WSGI + environment. The :term:`router` passes the request as an argument to the + mapper. + +#. If any route matches, the route mapper adds attributes to the request: + ``matchdict`` and ``matched_route`` attributes are added to the request + object. The former contains a dictionary representing the matched dynamic + elements of the request's ``PATH_INFO`` value, the latter contains the + :class:`~pyramid.interfaces.IRoute` object representing the route which + matched. The root object associated with the route found is also + generated: if the :term:`route configuration` which matched has an + associated a ``factory`` argument, this factory is used to generate the + root object, otherwise a default :term:`root factory` is used. + +#. If a route match was *not* found, and a ``root_factory`` argument was + passed to the :term:`Configurator` constructor, that callable is used to + generate the root object. If the ``root_factory`` argument passed to the + Configurator constructor was ``None``, a default root factory is used to + generate a root object. + +#. The :app:`Pyramid` router calls a "traverser" function with the root + object and the request. The traverser function attempts to traverse the + root object (using any existing ``__getitem__`` on the root object and + subobjects) to find a :term:`context`. If the root object has no + ``__getitem__`` method, the root itself is assumed to be the context. The + exact traversal algorithm is described in :ref:`traversal_chapter`. The + traverser function returns a dictionary, which contains a :term:`context` + and a :term:`view name` as well as other ancillary information. + +#. The request is decorated with various names returned from the traverser + (such as ``context``, ``view_name``, and so forth), so they can be + accessed via e.g. ``request.context`` within :term:`view` code. + +#. A :class:`~pyramid.events.ContextFound` :term:`event` is sent to any + subscribers. #. :app:`Pyramid` looks up a :term:`view` callable using the context, the request, and the view name. If a view callable doesn't exist for this @@ -86,20 +79,17 @@ processing? :class:`~pyramid.httpexceptions.HTTPNotFound` exception, which is meant to be caught by a surrounding :term:`exception view`. -#. If a view callable was found, :app:`Pyramid` attempts to call - the view function. - -#. If an :term:`authorization policy` is in use, and the view was protected - by a :term:`permission`, :app:`Pyramid` passes the context, the request, - and the view_name to a function which determines whether the view being - asked for can be executed by the requesting user, based on credential - information in the request and 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.httpexceptions.HTTPForbidden` exception, which is meant - to be called by a surrounding :term:`exception view`. - -#. If any exception was raised within a :term:`root factory`, by +#. If a view callable was found, :app:`Pyramid` attempts to call it. If an + :term:`authorization policy` is in use, and the view configuration is + protected by a :term:`permission`, :app:`Pyramid` determines whether the + view callable being asked for can be executed by the requesting user based + on credential information in the request and security information attached + to the context. If the view execution is allowed, :app:`Pyramid` calls + the view callable to obtain a response. If view execution is forbidden, + :app:`Pyramid` raises a :class:`~pyramid.httpexceptions.HTTPForbidden` + exception. + +#. If any exception is 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.httpexceptions.HTTPNotFound` or :class:`~pyramid.httpexceptions.HTTPForbidden`), the router catches the @@ -128,9 +118,8 @@ processing? .. image:: router.png -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:`urldispatch_chapter`, :ref:`views_chapter`, and -:ref:`events_chapter`. +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:`urldispatch_chapter`, :ref:`views_chapter`, and :ref:`events_chapter`. diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index f4ebef154..a7fc5d33c 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -24,11 +24,11 @@ The Startup Process ------------------- The easiest and best-documented way to start and serve a :app:`Pyramid` -application is to use the ``pserve`` command against a -:term:`PasteDeploy` ``.ini`` file. This uses the ``.ini`` file to infer -settings and starts a server listening on a port. For the purposes of this -discussion, we'll assume that you are using this command to run your -:app:`Pyramid` application. +application is to use the ``pserve`` command against a :term:`PasteDeploy` +``.ini`` file. This uses the ``.ini`` file to infer settings and starts a +server listening on a port. For the purposes of this discussion, we'll +assume that you are using this command to run your :app:`Pyramid` +application. Here's a high-level time-ordered overview of what happens when you press ``return`` after running ``pserve development.ini``. @@ -56,11 +56,12 @@ Here's a high-level time-ordered overview of what happens when you press #. The framework finds all :mod:`logging` related configuration in the ``.ini`` file and uses it to configure the Python standard library logging - system for this application. + system for this application. See :ref:`logging_config` for more + information. -#. The application's *constructor* (named by the entry point reference or +#. The application's *constructor* named by the entry point reference or dotted Python name on the ``use=`` line of the section representing your - :app:`Pyramid` application) is passed the key/value parameters mentioned + :app:`Pyramid` application is passed the key/value parameters mentioned within the section in which it's defined. The constructor is meant to return a :term:`router` instance, which is a :term:`WSGI` application. @@ -76,12 +77,14 @@ Here's a high-level time-ordered overview of what happens when you press Note that the constructor function accepts a ``global_config`` argument, which is a dictionary of key/value pairs mentioned in the ``[DEFAULT]`` - section of an ``.ini`` file. It also accepts a ``**settings`` argument, - which collects another set of arbitrary key/value pairs. The arbitrary - key/value pairs received by this function in ``**settings`` will be - composed of all the key/value pairs that are present in the ``[app:main]`` - section (except for the ``use=`` setting) when this function is called by - when you run ``pserve``. + section of an ``.ini`` file (if `[DEFAULT] + <http://docs.pylonsproject.org/projects/pyramid/dev/narr/paste.html#defaults-section-of-a-pastedeploy-ini-file>`__ + is present). It also accepts a ``**settings`` argument, which collects + another set of arbitrary key/value pairs. The arbitrary key/value pairs + received by this function in ``**settings`` will be composed of all the + key/value pairs that are present in the ``[app:main]`` section (except for + the ``use=`` setting) when this function is called by when you run + ``pserve``. Our generated ``development.ini`` file looks like so: @@ -95,7 +98,8 @@ Here's a high-level time-ordered overview of what happens when you press will receive the key/value pairs ``{'pyramid.reload_templates':'true', 'pyramid.debug_authorization':'false', 'pyramid.debug_notfound':'false', 'pyramid.debug_routematch':'false', 'pyramid.debug_templates':'true', - 'pyramid.default_locale_name':'en'}``. + 'pyramid.default_locale_name':'en'}``. See :ref:`environment_chapter` for + the meanings of these keys. #. The ``main`` function first constructs a :class:`~pyramid.config.Configurator` instance, passing a root resource @@ -103,10 +107,6 @@ Here's a high-level time-ordered overview of what happens when you press ``settings`` dictionary captured via the ``**settings`` kwarg as its ``settings`` argument. - The root resource factory is invoked on every request to retrieve the - application's root resource. It is not called during startup, only when a - request is handled. - The ``settings`` dictionary contains all the options in the ``[app:main]`` section of our .ini file except the ``use`` option (which is internal to PasteDeploy) such as ``pyramid.reload_templates``, diff --git a/docs/narr/tb_introspector.png b/docs/narr/tb_introspector.png Binary files differnew file mode 100644 index 000000000..4ae406a86 --- /dev/null +++ b/docs/narr/tb_introspector.png diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index fb9dd56c2..11318d9eb 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -403,14 +403,6 @@ The language definition documentation for Chameleon ZPT-style templates is available from `the Chameleon website <http://chameleon.repoze.org/>`_. -.. warning:: - - :term:`Chameleon` only works on :term:`CPython` platforms and - :term:`Google App Engine`. On :term:`Jython` and other non-CPython - platforms, you should use Mako (see :ref:`mako_templates`) or - ``pyramid_jinja2`` instead. See - :ref:`available_template_system_bindings`. - Given a :term:`Chameleon` ZPT template named ``foo.pt`` in a directory in your application named ``templates``, you can render the template as a :term:`renderer` like so: diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst index 05e851fde..7ee432fa7 100644 --- a/docs/narr/testing.rst +++ b/docs/narr/testing.rst @@ -52,7 +52,7 @@ The suggested mechanism for unit and integration testing of a :app:`Pyramid` application is the Python :mod:`unittest` module. Although this module is named :mod:`unittest`, it is actually capable of driving both unit and integration tests. A good :mod:`unittest` tutorial is available within `Dive -Into Python <http://diveintopython.org/unit_testing/index.html>`_ by Mark +Into Python <http://diveintopython.nfshost.com/unit_testing/index.html>`_ by Mark Pilgrim. :app:`Pyramid` provides a number of facilities that make unit, integration, diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 9ceb20f21..35613ea1b 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -65,7 +65,7 @@ example: config.add_view(myview, route_name='myroute') When a :term:`view callable` added to the configuration by way of -:meth:`~pyramid.config.Configurator.add_view` bcomes associated with a route +:meth:`~pyramid.config.Configurator.add_view` becomes associated with a route via its ``route_name`` predicate, that view callable will always be found and invoked when the associated route pattern matches during a request. @@ -101,7 +101,7 @@ that references ``myroute`` as a ``route_name`` parameter: def myview(request): return Response('OK') -THe above combination of ``add_route`` and ``scan`` is completely equivalent +The above combination of ``add_route`` and ``scan`` is completely equivalent to using the previous combination of ``add_route`` and ``add_view``. .. index:: diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index af5d7f242..03000629c 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -621,6 +621,7 @@ against the ``amethod`` method could be spelled equivalently as the below: def amethod(self): return Response('hello') + .. index:: single: add_view @@ -658,6 +659,186 @@ configurations, you don't need to issue a :term:`scan` in order for the view configuration to take effect. .. index:: + single: view_defaults class decorator + +.. _view_defaults: + +``@view_defaults`` Class Decorator +---------------------------------- + +.. note:: + + This feature is new in Pyramid 1.3. + +If you use a class as a view, you can use the +:class:`pyramid.view.view_defaults` class decorator on the class to provide +defaults to the view configuration information used by every ``@view_config`` +decorator that decorates a method of that class. + +For instance, if you've got a class that has methods that represent "REST +actions", all which are mapped to the same route, but different request +methods, instead of this: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + class RESTView(object): + def __init__(self, request): + self.request = request + + @view_config(route_name='rest', request_method='GET') + def get(self): + return Response('get') + + @view_config(route_name='rest', request_method='POST') + def post(self): + return Response('post') + + @view_config(route_name='rest', request_method='DELETE') + def delete(self): + return Response('delete') + +You can do this: + +.. code-block:: python + :linenos: + + from pyramid.view import view_defaults + from pyramid.view import view_config + from pyramid.response import Response + + @view_defaults(route_name='rest') + class RESTView(object): + def __init__(self, request): + self.request = request + + @view_config(request_method='GET') + def get(self): + return Response('get') + + @view_config(request_method='POST') + def post(self): + return Response('post') + + @view_config(request_method='DELETE') + def delete(self): + return Response('delete') + +In the above example, we were able to take the ``route_name='rest'`` argument +out of the call to each individual ``@view_config`` statement, because we +used a ``@view_defaults`` class decorator to provide the argument as a +default to each view method it possessed. + +Arguments passed to ``@view_config`` will override any default passed to +``@view_defaults``. + +The ``view_defaults`` class decorator can also provide defaults to the +:meth:`pyramid.config.Configurator.add_view` directive when a decorated class +is passed to that directive as its ``view`` argument. For example, instead +of this: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.config import Configurator + + class RESTView(object): + def __init__(self, request): + self.request = request + + def get(self): + return Response('get') + + def post(self): + return Response('post') + + def delete(self): + return Response('delete') + + if __name__ == '__main__': + config = Configurator() + config.add_route('rest', '/rest') + config.add_view( + RESTView, route_name='rest', attr='get', request_method='GET') + config.add_view( + RESTView, route_name='rest', attr='post', request_method='POST') + config.add_view( + RESTView, route_name='rest', attr='delete', request_method='DELETE') + +To reduce the amount of repetion in the ``config.add_view`` statements, we +can move the ``route_name='rest'`` argument to a ``@view_default`` class +decorator on the RESTView class: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + from pyramid.config import Configurator + + @view_defaults(route_name='rest') + class RESTView(object): + def __init__(self, request): + self.request = request + + def get(self): + return Response('get') + + def post(self): + return Response('post') + + def delete(self): + return Response('delete') + + if __name__ == '__main__': + config = Configurator() + config.add_route('rest', '/rest') + config.add_view(RESTView, attr='get', request_method='GET') + config.add_view(RESTView, attr='post', request_method='POST') + config.add_view(RESTView, attr='delete', request_method='DELETE') + +:class:`pyramid.view.view_defaults` accepts the same set of arguments that +:class:`pyramid.view.view_config` does, and they have the same meaning. Each +argument passed to ``view_defaults`` provides a default for the view +configurations of methods of the class it's decorating. + +Normal Python inheritance rules apply to defaults added via +``view_defaults``. For example: + +.. code-block:: python + :linenos: + + @view_defaults(route_name='rest') + class Foo(object): + pass + + class Bar(Foo): + pass + +The ``Bar`` class above will inherit its view defaults from the arguments +passed to the ``view_defaults`` decorator of the ``Foo`` class. To prevent +this from happening, use a ``view_defaults`` decorator without any arguments +on the subclass: + +.. code-block:: python + :linenos: + + @view_defaults(route_name='rest') + class Foo(object): + pass + + @view_defaults() + class Bar(Foo): + pass + +The ``view_defaults`` decorator only works as a class decorator; using it +against a function or a method will produce nonsensical results. + +.. index:: single: view security pair: security; view diff --git a/docs/tutorials/wiki/NOTE-relocatable.txt b/docs/tutorials/wiki/NOTE-relocatable.txt new file mode 100644 index 000000000..cec2639f3 --- /dev/null +++ b/docs/tutorials/wiki/NOTE-relocatable.txt @@ -0,0 +1,13 @@ +We specifically use relative package references where possible so this demo +works even if the user names their package (in the 'bin/paster create -t +zodb ...' step) something other than 'tutorial'. + +Specifically: + +- use relative imports +- use plain relative URLs for resources (like stylesheets and images) in + page templates. + +Direct uses of the package name, like in __init__.py 'config.scan()' +statements, are already adjusted by the paster/pcreate, so we don't have to +worry about them. diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 1835ce7ea..fa18d4a41 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -4,7 +4,7 @@ Adding Authorization Our application currently allows anyone with access to the server to view, edit, and add pages to our wiki. For purposes of demonstration we'll change -our application to allow people whom are members of a *group* named +our application to allow people who are members of a *group* named ``group:editors`` to add and edit wiki pages but we'll continue allowing anyone with access to the server to view pages. :app:`Pyramid` provides facilities for :term:`authorization` and :term:`authentication`. We'll make @@ -27,8 +27,8 @@ The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/ <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/>`_. -Adding Authentication and Authorization Policies -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add Authentication and Authorization Policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We'll change our package's ``__init__.py`` file to enable an ``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable @@ -42,7 +42,7 @@ declarative security checking. We need to import the new policies: Then, we'll add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 20-25 + :lines: 17-22 :linenos: :language: python @@ -60,8 +60,8 @@ look like so: :linenos: :language: python -Adding ``security.py`` -~~~~~~~~~~~~~~~~~~~~~~ +Add ``security.py`` +~~~~~~~~~~~~~~~~~~~ Add a ``security.py`` module within your package (in the same directory as ``__init__.py``, ``views.py``, etc.) with the following @@ -73,17 +73,16 @@ content: The ``groupfinder`` function defined here is an :term:`authentication policy` "callback"; it is a callable that accepts a userid and a request. If the -userid exists in the system, the callback will -return a sequence of group identifiers (or an empty sequence if the user -isn't a member of any groups). If the userid *does not* exist in the system, -the callback will return ``None``. In a production system, user and group data will -most often come from a database, but here we use "dummy" data to represent -user and groups sources. Note that the ``editor`` user is a member of the -``group:editors`` group in our dummy group data (the ``GROUPS`` data -structure). - -Giving Our Root Resource an ACL -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +userid exists in the system, the callback will return a sequence of group +identifiers (or an empty sequence if the user isn't a member of any groups). +If the userid *does not* exist in the system, the callback will return +``None``. In a production system, user and group data will most often come +from a database, but here we use "dummy" data to represent user and groups +sources. Note that the ``editor`` user is a member of the ``group:editors`` +group in our dummy group data (the ``GROUPS`` data structure). + +Give Our Root Resource an ACL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 @@ -119,8 +118,8 @@ Our resulting ``models.py`` file will now look like so: :linenos: :language: python -Adding Login and Logout Views -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add Login and Logout Views +~~~~~~~~~~~~~~~~~~~~~~~~~~ We'll add a ``login`` view which renders a login form and processes the post from the login form, checking credentials. @@ -129,18 +128,24 @@ We'll also add a ``logout`` view to our application and provide a link to it. This view will clear the credentials of the logged in user and redirect back to the front page. -We'll add a different file (for presentation convenience) to add login -and logout views. Add a file named ``login.py`` to your application -(in the same directory as ``views.py``) with the following content: +We'll add these views to the existing ``views.py`` file we have in our +project. Here's what the ``login`` view callable will look like: + +.. literalinclude:: src/authorization/tutorial/views.py + :pyobject: login + :linenos: + :language: python + +Here's what the ``logout`` view callable will look like: -.. literalinclude:: src/authorization/tutorial/login.py +.. literalinclude:: src/authorization/tutorial/views.py + :pyobject: logout :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. +Note that the ``login`` view callable 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 @@ -157,14 +162,18 @@ 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 -~~~~~~~~~~~~~~~~~~~~~~~ +Note that we're relying on some additional imports within the bodies of these +views (e.g. ``remember`` and ``forget``). We'll see a rendering of the +entire views.py file a little later here to show you where those come from. -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 -into its template. We'll add something like this to each view body: +Change Existing Views +~~~~~~~~~~~~~~~~~~~~~ + +In order to indicate whether the current user is logged in, we need to change +each of our ``view_page``, ``edit_page`` and ``add_page`` views in +``views.py`` to pass a "logged in" parameter into its template. We'll add +something like this to each view body: -.. ignore-next-block .. code-block:: python :linenos: @@ -175,7 +184,6 @@ We'll then change the return value of each view that has an associated ``renderer`` to pass the resulting ``logged_in`` value to the template. For example: -.. ignore-next-block .. code-block:: python :linenos: @@ -184,8 +192,8 @@ template. For example: logged_in = logged_in, edit_url = edit_url) -Adding ``permission`` Declarations to our ``view_config`` Decorators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add ``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` @@ -216,11 +224,11 @@ decorators. To do so, within ``views.py``: function consults the ``GROUPS`` data structure. This means that the ``editor`` user can add and edit pages. -Adding the ``login.pt`` Template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add the ``login.pt`` Template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Add a ``login.pt`` template to your templates directory. It's -referred to within the login view we just added to ``login.py``. +referred to within the login view we just added to ``views.py``. .. literalinclude:: src/authorization/tutorial/templates/login.pt :language: xml @@ -241,8 +249,8 @@ class="app-welcome align-right">`` div: <a href="${request.application_url}/logout">Logout</a> </span> -Seeing Our Changes To ``views.py`` and our Templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +See Our Changes To ``views.py`` and our Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Our ``views.py`` module will look something like this when we're done: @@ -262,8 +270,8 @@ Our ``view.pt`` template will look something like this when we're done: :linenos: :language: xml -Viewing the Application in a Browser -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +View the Application in a Browser +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can finally examine our application in a browser. The views we'll try are as follows: diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 47cac597b..56f817a85 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -2,7 +2,7 @@ Basic Layout ============ -The starter files generated by the ``pyramid_zodb`` scaffold are basic, but +The starter files generated by the ``zodb`` scaffold 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. @@ -10,8 +10,8 @@ The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/ <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/>`_. -App Startup with ``__init__.py`` --------------------------------- +Appplication Configuration with ``__init__.py`` +------------------------------------------------ A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file. Even if empty, this marks a directory as a Python @@ -35,7 +35,7 @@ point happens to be the ``main`` function within the file named #. *Line 12*. We construct a :term:`Configurator` with a :term:`root factory` and the settings keywords parsed by :term:`PasteDeploy`. The root - factory is named ``get_root``. + factory is named ``root_factory``. #. *Line 13*. Register a 'static view' which answers requests which start with with URL path ``/static`` using the @@ -45,16 +45,19 @@ point happens to be the ``main`` function within the file named ``http://localhost:6543/static/`` and below. The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``. the The second argument of this tag is the "path", - which is an :term:`asset specification`, so it finds the resources it - should serve within the ``static`` directory inside the ``tutorial`` - package. + which is a relative :term:`asset specification`, so it finds the resources + it should serve within the ``static`` directory inside the ``tutorial`` + package. The scaffold could have alternately used an *absolute* asset + specification as the path (``tutorial:static``) but it does not. #. *Line 14*. Perform a :term:`scan`. A scan will find :term:`configuration - decoration`, such as view configuration decorators - (e.g. ``@view_config``) in the source code of the ``tutorial`` package and - will take actions based on these decorators. The argument to - :meth:`~pyramid.config.Configurator.scan` is the package name to scan, - which is ``tutorial``. + decoration`, such as view configuration decorators (e.g. ``@view_config``) + in the source code of the ``tutorial`` package and will take actions based + on these decorators. We don't pass any arguments to + :meth:`~pyramid.config.Configurator.scan`, which implies that the scan + should take place in the current package (in this case, ``tutorial``). + The scaffold could have equivalently said ``config.scan('tutorial')`` but + it chose to omit the package name argument. #. *Line 15*. Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method @@ -69,7 +72,7 @@ hierarchically in a :term:`resource tree`. This tree is consulted by tree represents the site structure, but it *also* represents the :term:`domain model` of the application, because each resource is a node stored persistently in a :term:`ZODB` database. The ``models.py`` file is -where the ``pyramid_zodb`` scaffold put the classes that implement our +where the ``zodb`` scaffold put the classes that implement our resource objects, each of which happens also to be a domain model object. Here is the source for ``models.py``: @@ -119,7 +122,7 @@ Let's try to understand the components in this module: decoration` to perform a :term:`view configuration` registration. This view configuration registration will be activated when the application is started. It will be activated by virtue of it being found as the result - of a :term:`scan` (when Line 17 of ``__init__.py`` is run). + of a :term:`scan` (when Line 14 of ``__init__.py`` is run). The ``@view_config`` decorator accepts a number of keyword arguments. We use two keyword arguments here: ``context`` and ``renderer``. @@ -131,12 +134,15 @@ Let's try to understand the components in this module: model, this view callable will be invoked. The ``renderer`` argument names an :term:`asset specification` of - ``tutorial:templates/mytemplate.pt``. This asset specification points at - a :term:`Chameleon` template which lives in the ``mytemplate.pt`` file + ``templates/mytemplate.pt``. This asset specification points at a + :term:`Chameleon` template which lives in the ``mytemplate.pt`` file within the ``templates`` directory of the ``tutorial`` package. And indeed if you look in the ``templates`` directory of this package, you'll see a ``mytemplate.pt`` template file, which renders the default home page - of the generated project. + of the generated project. This asset specification is *relative* (to the + view.py's current package). We could have alternately an used the + absolute asset specification ``tutorial:templates/mytemplate.pt``, but + chose to use the relative version. Since this call to ``@view_config`` doesn't pass a ``name`` argument, the ``my_view`` function which it decorates represents the "default" view @@ -144,7 +150,7 @@ Let's try to understand the components in this module: #. *Lines 5-6*. We define a :term:`view callable` named ``my_view``, which we decorated in the step above. This view callable is a *function* we - write generated by the ``pyramid_zodb`` scaffold that is given a + write generated by the ``zodb`` scaffold that is given a ``request`` and which returns a dictionary. The ``mytemplate.pt`` :term:`renderer` named by the asset specification in the step above will convert this dictionary to a :term:`response` on our behalf. diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index ee9c13ab2..cdf3b6092 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -18,8 +18,8 @@ The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/models/ <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/models/>`_. -Deleting the Database ---------------------- +Delete the Database +------------------- In the next step, we're going to remove the ``MyModel`` Python model class from our ``models.py`` file. Since this class is referred to within @@ -30,8 +30,8 @@ directory before proceeding any further. It's always fine to do this as long as you don't care about the content of the database; the database itself will be recreated as necessary. -Making Edits to ``models.py`` ------------------------------ +Edit ``models.py`` +------------------ .. note:: @@ -73,8 +73,8 @@ front page) into the Wiki within the ``appmaker``. This will provide :term:`traversal` a :term:`resource tree` to work against when it attempts to resolve URLs to resources. -Looking at the Result of Our Edits to ``models.py`` ---------------------------------------------------- +Look at the Result of Our Edits to ``models.py`` +------------------------------------------------ The result of all of our edits to ``models.py`` will end up looking something like this: @@ -83,8 +83,8 @@ something like this: :linenos: :language: python -Viewing the Application in a Browser ------------------------------------- +View the Application in a Browser +--------------------------------- We can't. At this point, our system is in a "non-runnable" state; we'll need to change view-related files in the next chapter to be able to start the diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index c21367559..371cae8eb 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -293,12 +293,10 @@ 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/pylons.css`` by virtue of the call to +e.g. ``/static/pylons.css`` by virtue of the call to ``add_static_view`` directive we've made in the ``__init__.py`` file. Any number and type of static assets can be placed in this directory (or -subdirectories) and are just referred to by URL or by using the convenience -method ``static_url`` e.g. ``request.static_url('{{package}}:static/foo.css')`` -within templates. +subdirectories) and are just referred to by URL. Viewing the Application in a Browser ==================================== diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index c55c310ef..330b17c86 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -127,8 +127,8 @@ Preparation, Windows .. _making_a_project: -Making a Project -================ +Make a Project +============== Your next step is to create a project. :app:`Pyramid` supplies a variety of scaffolds to generate sample projects. For this tutorial, we will use the @@ -149,14 +149,18 @@ On Windows: c:\pyramidtut> Scripts\pcreate -s zodb tutorial +.. note:: You don't have to call it `tutorial` -- the code uses + relative paths for imports and finding templates and static + resources. + .. note:: If you are using Windows, the ``zodb`` scaffold doesn't currently deal gracefully with installation into a location that contains spaces in the path. If you experience startup problems, try putting both the virtualenv and the project into directories that do not contain spaces in their paths. -Installing the Project in "Development Mode" -============================================ +Install the Project in "Development Mode" +========================================= In order to do development on the project easily, you must "register" the project as a development egg in your workspace using the @@ -180,8 +184,8 @@ On Windows: .. _running_tests: -Running the Tests -================= +Run the Tests +============= After you've installed the project in development mode, you may run the tests for the project. @@ -198,48 +202,53 @@ On Windows: c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q -Starting the Application -======================== +Expose Test Coverage Information +================================ -Start the application. +You can run the ``nosetests`` command to see test coverage +information. This runs the tests in the same way that ``setup.py +test`` does but provides additional "coverage" information, exposing +which lines of your project are "covered" (or not covered) by the +tests. On UNIX: .. code-block:: text - $ ../bin/pserve development.ini --reload + $ ../bin/nosetests --cover-package=tutorial --cover-erase --with-coverage On Windows: .. code-block:: text - c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload + c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^ + --cover-erase --with-coverage -Exposing Test Coverage Information -================================== +Looks like the code in the ``zodb`` scaffold for ZODB projects is +missing some test coverage, particularly in the file named +``models.py``. -You can run the ``nosetests`` command to see test coverage -information. This runs the tests in the same way that ``setup.py -test`` does but provides additional "coverage" information, exposing -which lines of your project are "covered" (or not covered) by the -tests. +Start the Application +===================== + +Start the application. On UNIX: .. code-block:: text - $ ../bin/nosetests --cover-package=tutorial --cover-erase --with-coverage + $ ../bin/pserve development.ini --reload On Windows: .. code-block:: text - c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^ - --cover-erase --with-coverage + c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload -Looks like the code in the ``pyramid_zodb`` scaffold for ZODB projects is -missing some test coverage, particularly in the file named -``models.py``. +.. note:: + + Your OS firewall, if any, may pop up a dialog asking for authorization + to allow python to accept incoming network connections. Visit the Application in a Browser ================================== @@ -252,10 +261,10 @@ page. You can read more about the purpose of the icon at :ref:`debug_toolbar`. It allows you to get information about your application while you develop. -Decisions the ``pyramid_zodb`` Scaffold Has Made For You -======================================================== +Decisions the ``zodb`` Scaffold Has Made For You +================================================ -Creating a project using the ``pyramid_zodb`` scaffold makes the following +Creating a project using the ``zodb`` scaffold makes the following assumptions: - you are willing to use :term:`ZODB` as persistent storage diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 2d6eb5ecb..20ee685ee 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -4,8 +4,8 @@ from pyramid_zodbconn import get_connection from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy -from tutorial.models import appmaker -from tutorial.security import groupfinder +from .models import appmaker +from .security import groupfinder def root_factory(request): conn = get_connection(request) @@ -20,6 +20,6 @@ def main(global_config, **settings): config = Configurator(root_factory=root_factory, settings=settings, authentication_policy=authn_policy, authorization_policy=authz_policy) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - config.scan('tutorial') + config.add_static_view('static', 'static', cache_max_age=3600) + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/authorization/tutorial/login.py b/docs/tutorials/wiki/src/authorization/tutorial/login.py deleted file mode 100644 index d608a7d0b..000000000 --- a/docs/tutorials/wiki/src/authorization/tutorial/login.py +++ /dev/null @@ -1,44 +0,0 @@ -from pyramid.httpexceptions import HTTPFound - -from pyramid.security import remember -from pyramid.security import forget -from pyramid.view import view_config - -from tutorial.security import USERS - -@view_config(context='tutorial.models.Wiki', name='login', - renderer='templates/login.pt') -@view_config(context='pyramid.httpexceptions.HTTPForbidden', - renderer='templates/login.pt') -def login(request): - login_url = request.resource_url(request.context, 'login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use the login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' - login = '' - password = '' - if 'form.submitted' in request.params: - login = request.params['login'] - password = request.params['password'] - if USERS.get(login) == password: - headers = remember(request, login) - return HTTPFound(location = came_from, - headers = headers) - message = 'Failed login' - - return dict( - message = message, - url = request.application_url + '/login', - came_from = came_from, - login = login, - password = password, - ) - -@view_config(context='tutorial.models.Wiki', name='logout') -def logout(request): - headers = forget(request) - return HTTPFound(location = request.resource_url(request.context), - headers = headers) - diff --git a/docs/tutorials/wiki/src/authorization/tutorial/security.py b/docs/tutorials/wiki/src/authorization/tutorial/security.py index cfd13071e..d88c9c71f 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/security.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/security.py @@ -5,4 +5,3 @@ GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) - diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt index f9da6c414..0d0738f7f 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt index 64e592ea9..2c7235761 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt index 14b88d16a..3597c679b 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <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="shortcut icon" href="/static/favicon.ico" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt index d207a0c23..9dd6540cf 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py index a4a4e2754..77e7cce29 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py @@ -5,7 +5,7 @@ from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Page + from .models import Page return Page def _makeOne(self, data=u'some data'): @@ -14,11 +14,11 @@ class PageModelTests(unittest.TestCase): def test_constructor(self): instance = self._makeOne() self.assertEqual(instance.data, u'some data') - + class WikiModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Wiki + from .models import Wiki return Wiki def _makeOne(self): @@ -31,7 +31,7 @@ class WikiModelTests(unittest.TestCase): class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): - from tutorial.models import appmaker + from .models import appmaker return appmaker(zodb_root) def test_it(self): @@ -42,7 +42,7 @@ class AppmakerTests(unittest.TestCase): class ViewWikiTests(unittest.TestCase): def test_it(self): - from tutorial.views import view_wiki + from .views import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) @@ -50,7 +50,7 @@ class ViewWikiTests(unittest.TestCase): class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import view_page + from .views import view_page return view_page(context, request) def test_it(self): @@ -72,11 +72,11 @@ class ViewPageTests(unittest.TestCase): '</p>\n</div>\n') self.assertEqual(info['edit_url'], 'http://example.com/thepage/edit_page') - - + + class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import add_page + from .views import add_page return add_page(context, request) def test_it_notsubmitted(self): @@ -88,7 +88,7 @@ class AddPageTests(unittest.TestCase): self.assertEqual(info['save_url'], request.resource_url( context, 'add_page', 'AnotherPage')) - + def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, @@ -102,7 +102,7 @@ class AddPageTests(unittest.TestCase): class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import edit_page + from .views import edit_page return edit_page(context, request) def test_it_notsubmitted(self): @@ -112,7 +112,7 @@ class EditPageTests(unittest.TestCase): self.assertEqual(info['page'], context) self.assertEqual(info['save_url'], request.resource_url(context, 'edit_page')) - + def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py index a570410ca..2f0502c17 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py @@ -2,19 +2,26 @@ from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound + from pyramid.view import view_config -from pyramid.security import authenticated_userid -from tutorial.models import Page +from pyramid.security import ( + authenticated_userid, + remember, + forget, + ) + +from .security import USERS +from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='tutorial.models.Wiki', permission='view') +@view_config(context='.models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='tutorial.models.Page', +@view_config(context='.models.Page', renderer='templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ @@ -38,7 +45,7 @@ def view_page(context, request): return dict(page = context, content = content, edit_url = edit_url, logged_in = logged_in) -@view_config(name='add_page', context='tutorial.models.Wiki', +@view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', permission='edit') def add_page(context, request): @@ -59,7 +66,7 @@ def add_page(context, request): return dict(page = page, save_url = save_url, logged_in = logged_in) -@view_config(name='edit_page', context='tutorial.models.Page', +@view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt', permission='edit') def edit_page(context, request): @@ -72,4 +79,39 @@ def edit_page(context, request): return dict(page = context, save_url = request.resource_url(context, 'edit_page'), logged_in = logged_in) - + +@view_config(context='.models.Wiki', name='login', + renderer='templates/login.pt') +@view_config(context='pyramid.httpexceptions.HTTPForbidden', + renderer='templates/login.pt') +def login(request): + login_url = request.resource_url(request.context, 'login') + referrer = request.url + if referrer == login_url: + referrer = '/' # never use the login form itself as came_from + came_from = request.params.get('came_from', referrer) + message = '' + login = '' + password = '' + if 'form.submitted' in request.params: + login = request.params['login'] + password = request.params['password'] + if USERS.get(login) == password: + headers = remember(request, login) + return HTTPFound(location = came_from, + headers = headers) + message = 'Failed login' + + return dict( + message = message, + url = request.application_url + '/login', + came_from = came_from, + login = login, + password = password, + ) + +@view_config(context='.models.Wiki', name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location = request.resource_url(request.context), + headers = headers) diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index e49a61129..b63933fc5 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -1,6 +1,6 @@ from pyramid.config import Configurator from pyramid_zodbconn import get_connection -from tutorial.models import appmaker +from .models import appmaker def root_factory(request): conn = get_connection(request) @@ -10,6 +10,6 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - config.scan('tutorial') + config.add_static_view('static', 'static', cache_max_age=3600) + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index f9f351c97..557e071ed 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <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="shortcut icon" href="/static/favicon.ico" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py index 1f3c3bb4d..8d2374be1 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py @@ -10,7 +10,7 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from tutorial.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'tutorial') diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py index 157b9ac8f..4265b6bf7 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py @@ -1,7 +1,6 @@ from pyramid.view import view_config -from tutorial.models import MyModel +from .models import MyModel -@view_config(context=MyModel, - renderer='tutorial:templates/mytemplate.pt') +@view_config(context=MyModel, renderer='templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index 2d637b9de..c59f36e7b 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -1,6 +1,6 @@ from pyramid.config import Configurator from pyramid_zodbconn import get_connection -from tutorial.models import appmaker +from .models import appmaker def root_factory(request): conn = get_connection(request) @@ -10,7 +10,7 @@ def main(global_config, **settings): """ This function returns a WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - config.scan('tutorial') + config.add_static_view('static', 'static', cache_max_age=3600) + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index 14b88d16a..3597c679b 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <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="shortcut icon" href="/static/favicon.ico" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py index 51c97a95d..9fd13a18d 100644 --- a/docs/tutorials/wiki/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki/src/models/tutorial/tests.py @@ -5,7 +5,7 @@ from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Page + from .models import Page return Page def _makeOne(self, data=u'some data'): @@ -14,11 +14,11 @@ class PageModelTests(unittest.TestCase): def test_constructor(self): instance = self._makeOne() self.assertEqual(instance.data, u'some data') - + class WikiModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Wiki + from .models import Wiki return Wiki def _makeOne(self): @@ -32,7 +32,7 @@ class WikiModelTests(unittest.TestCase): class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): - from tutorial.models import appmaker + from .models import appmaker return appmaker(zodb_root) def test_no_app_root(self): @@ -55,7 +55,7 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from tutorial.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'tutorial') diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py index 2346602c9..7c1f1d228 100644 --- a/docs/tutorials/wiki/src/models/tutorial/views.py +++ b/docs/tutorials/wiki/src/models/tutorial/views.py @@ -1,5 +1,5 @@ from pyramid.view import view_config -@view_config(renderer='tutorial:templates/mytemplate.pt') +@view_config(renderer='templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py index 2d6eb5ecb..20ee685ee 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py @@ -4,8 +4,8 @@ from pyramid_zodbconn import get_connection from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy -from tutorial.models import appmaker -from tutorial.security import groupfinder +from .models import appmaker +from .security import groupfinder def root_factory(request): conn = get_connection(request) @@ -20,6 +20,6 @@ def main(global_config, **settings): config = Configurator(root_factory=root_factory, settings=settings, authentication_policy=authn_policy, authorization_policy=authz_policy) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - config.scan('tutorial') + config.add_static_view('static', 'static', cache_max_age=3600) + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/tests/tutorial/login.py b/docs/tutorials/wiki/src/tests/tutorial/login.py deleted file mode 100644 index d608a7d0b..000000000 --- a/docs/tutorials/wiki/src/tests/tutorial/login.py +++ /dev/null @@ -1,44 +0,0 @@ -from pyramid.httpexceptions import HTTPFound - -from pyramid.security import remember -from pyramid.security import forget -from pyramid.view import view_config - -from tutorial.security import USERS - -@view_config(context='tutorial.models.Wiki', name='login', - renderer='templates/login.pt') -@view_config(context='pyramid.httpexceptions.HTTPForbidden', - renderer='templates/login.pt') -def login(request): - login_url = request.resource_url(request.context, 'login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use the login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' - login = '' - password = '' - if 'form.submitted' in request.params: - login = request.params['login'] - password = request.params['password'] - if USERS.get(login) == password: - headers = remember(request, login) - return HTTPFound(location = came_from, - headers = headers) - message = 'Failed login' - - return dict( - message = message, - url = request.application_url + '/login', - came_from = came_from, - login = login, - password = password, - ) - -@view_config(context='tutorial.models.Wiki', name='logout') -def logout(request): - headers = forget(request) - return HTTPFound(location = request.resource_url(request.context), - headers = headers) - diff --git a/docs/tutorials/wiki/src/tests/tutorial/security.py b/docs/tutorials/wiki/src/tests/tutorial/security.py index cfd13071e..d88c9c71f 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/security.py +++ b/docs/tutorials/wiki/src/tests/tutorial/security.py @@ -5,4 +5,3 @@ GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) - diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt index f9da6c414..0d0738f7f 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt index 64e592ea9..2c7235761 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt index 14b88d16a..3597c679b 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <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="shortcut icon" href="/static/favicon.ico" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt index d207a0c23..9dd6540cf 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py index b1d4e68c3..81f7a1882 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/tests.py +++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py @@ -5,7 +5,7 @@ from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Page + from .models import Page return Page def _makeOne(self, data=u'some data'): @@ -18,7 +18,7 @@ class PageModelTests(unittest.TestCase): class WikiModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Wiki + from .models import Wiki return Wiki def _makeOne(self): @@ -31,7 +31,7 @@ class WikiModelTests(unittest.TestCase): class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): - from tutorial.models import appmaker + from .models import appmaker return appmaker(zodb_root) def test_it(self): @@ -42,7 +42,7 @@ class AppmakerTests(unittest.TestCase): class ViewWikiTests(unittest.TestCase): def test_it(self): - from tutorial.views import view_wiki + from .views import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) @@ -50,7 +50,7 @@ class ViewWikiTests(unittest.TestCase): class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import view_page + from .views import view_page return view_page(context, request) def test_it(self): @@ -76,7 +76,7 @@ class ViewPageTests(unittest.TestCase): class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import add_page + from .views import add_page return add_page(context, request) def test_it_notsubmitted(self): @@ -102,7 +102,7 @@ class AddPageTests(unittest.TestCase): class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import edit_page + from .views import edit_page return edit_page(context, request) def test_it_notsubmitted(self): @@ -133,7 +133,7 @@ class FunctionalTests(unittest.TestCase): def setUp(self): import tempfile import os.path - from tutorial import main + from . import main self.tmpdir = tempfile.mkdtemp() dbpath = os.path.join( self.tmpdir, 'test.db') diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py index a570410ca..2f0502c17 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/views.py +++ b/docs/tutorials/wiki/src/tests/tutorial/views.py @@ -2,19 +2,26 @@ from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound + from pyramid.view import view_config -from pyramid.security import authenticated_userid -from tutorial.models import Page +from pyramid.security import ( + authenticated_userid, + remember, + forget, + ) + +from .security import USERS +from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='tutorial.models.Wiki', permission='view') +@view_config(context='.models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='tutorial.models.Page', +@view_config(context='.models.Page', renderer='templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ @@ -38,7 +45,7 @@ def view_page(context, request): return dict(page = context, content = content, edit_url = edit_url, logged_in = logged_in) -@view_config(name='add_page', context='tutorial.models.Wiki', +@view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', permission='edit') def add_page(context, request): @@ -59,7 +66,7 @@ def add_page(context, request): return dict(page = page, save_url = save_url, logged_in = logged_in) -@view_config(name='edit_page', context='tutorial.models.Page', +@view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt', permission='edit') def edit_page(context, request): @@ -72,4 +79,39 @@ def edit_page(context, request): return dict(page = context, save_url = request.resource_url(context, 'edit_page'), logged_in = logged_in) - + +@view_config(context='.models.Wiki', name='login', + renderer='templates/login.pt') +@view_config(context='pyramid.httpexceptions.HTTPForbidden', + renderer='templates/login.pt') +def login(request): + login_url = request.resource_url(request.context, 'login') + referrer = request.url + if referrer == login_url: + referrer = '/' # never use the login form itself as came_from + came_from = request.params.get('came_from', referrer) + message = '' + login = '' + password = '' + if 'form.submitted' in request.params: + login = request.params['login'] + password = request.params['password'] + if USERS.get(login) == password: + headers = remember(request, login) + return HTTPFound(location = came_from, + headers = headers) + message = 'Failed login' + + return dict( + message = message, + url = request.application_url + '/login', + came_from = came_from, + login = login, + password = password, + ) + +@view_config(context='.models.Wiki', name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location = request.resource_url(request.context), + headers = headers) diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index 009013b3f..957a0b705 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -10,6 +10,6 @@ def main(global_config, **settings): """ This function returns a WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) + config.add_static_view('static', 'static', cache_max_age=3600) config.scan('tutorial') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt index 6dbb0edde..24ed2e592 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt index 14b88d16a..3597c679b 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <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="shortcut icon" href="/static/favicon.ico" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <link rel="stylesheet" href="${request.static_url('tutorial:static/pylons.css')}" type="text/css" media="screen" charset="utf-8" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt index 537ae3a15..424c4302a 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py index 245cda682..016f5b6bb 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views.py +++ b/docs/tutorials/wiki/src/views/tutorial/views.py @@ -4,17 +4,16 @@ import re from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config -from tutorial.models import Page +from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='tutorial.models.Wiki') +@view_config(context='.models.Wiki') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='tutorial.models.Page', - renderer='tutorial:templates/view.pt') +@view_config(context='.models.Page', renderer='templates/view.pt') def view_page(context, request): wiki = context.__parent__ @@ -33,8 +32,8 @@ def view_page(context, request): edit_url = request.resource_url(context, 'edit_page') return dict(page = context, content = content, edit_url = edit_url) -@view_config(name='add_page', context='tutorial.models.Wiki', - renderer='tutorial:templates/edit.pt') +@view_config(name='add_page', context='.models.Wiki', + renderer='templates/edit.pt') def add_page(context, request): name = request.subpath[0] if 'form.submitted' in request.params: @@ -50,8 +49,8 @@ def add_page(context, request): page.__parent__ = context return dict(page = page, save_url = save_url) -@view_config(name='edit_page', context='tutorial.models.Page', - renderer='tutorial:templates/edit.pt') +@view_config(name='edit_page', context='.models.Page', + renderer='templates/edit.pt') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] @@ -59,5 +58,3 @@ def edit_page(context, request): return dict(page = context, save_url = request.resource_url(context, 'edit_page')) - - diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst index 841baa8d1..1ddb8f408 100644 --- a/docs/tutorials/wiki/tests.rst +++ b/docs/tutorials/wiki/tests.rst @@ -6,22 +6,21 @@ We will now add tests for the models and the views and a few functional tests in the ``tests.py``. Tests ensure that an application works, and that it continues to work after some changes are made in the future. -Testing the Models -================== +Test the Models +=============== -We write tests for the model -classes and the appmaker. Changing ``tests.py``, we'll write a separate test -class for each model class, and we'll write a test class for the -``appmaker``. +We write tests for the model classes and the appmaker. Changing +``tests.py``, we'll write a separate test class for each model class, and +we'll write a test class for the ``appmaker``. To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a -result of the ``pyramid_zodb`` project generator. We'll add three test +result of the ``zodb`` project generator. We'll add three test classes: one for the ``Page`` model named ``PageModelTests``, one for the ``Wiki`` model named ``WikiModelTests``, and one for the appmaker named ``AppmakerTests``. -Testing the Views -================= +Test the Views +============== We'll modify our ``tests.py`` file, adding tests for each view function we added above. As a result, we'll *delete* the ``ViewTests`` test in the file, @@ -38,8 +37,8 @@ tested in the unit tests, like logging in, logging out, checking that the ``viewer`` user cannot add or edit pages, but the ``editor`` user can, and so on. -Viewing the results of all our edits to ``tests.py`` -==================================================== +View the results of all our edits to ``tests.py`` +================================================= Once we're done with the ``tests.py`` module, it will look a lot like the below: @@ -48,8 +47,8 @@ below: :linenos: :language: python -Running the Tests -================= +Run the Tests +============= We can run these tests by using ``setup.py test`` in the same way we did in :ref:`running_tests`. However, first we must edit our ``setup.py`` to diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index df5e228fd..ab04ea405 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -4,27 +4,22 @@ Adding Authorization ==================== -Our application currently allows anyone with access to the server to -view, edit, and add pages to our wiki. For purposes of demonstration -we'll change our application to allow only people whom possess a -specific username (`editor`) to add and edit wiki pages but we'll -continue allowing anyone with access to the server to view pages. -:app:`Pyramid` provides facilities for :term:`authorization` and -:term:`authentication`. We'll make use of both features to provide security -to our application. - -We will add an :term:`authentication policy` and an -:term:`authorization policy` to our :term:`application -registry`, add a ``security.py`` module, create a :term:`root factory` -with an :term:`ACL`, and add :term:`permission` declarations to -the ``edit_page`` and ``add_page`` views. - -Then we will add ``login`` and ``logout`` views, and modify the -existing views to make them return a ``logged_in`` flag to the -renderer. - -Finally, we will add a ``login.pt`` template and change the existing -``view.pt`` and ``edit.pt`` to show a "Logout" link when not logged in. +:app:`Pyramid` provides facilities for :term:`authentication` and +:term:`authorization`. We'll make use of both features to provide security +to our application. Our application currently allows anyone with access to +the server to view, edit, and add pages to our wiki. We'll change our +application to allow only people whom possess a specific username (`editor`) +to add and edit wiki pages but we'll continue allowing anyone with access to +the server to view pages. + +To do so, we'll add an :term:`authentication policy` and an +:term:`authorization policy`. We'll also add a ``security.py`` module, +create a :term:`root factory` with an :term:`ACL`, and add :term:`permission` +declarations to the ``edit_page`` and ``add_page`` views. Then we'll add +``login`` and ``logout`` views, and modify the existing views to make them +return a ``logged_in`` flag to the renderer. Finally, we will add a +``login.pt`` template and change the existing ``view.pt`` and ``edit.pt`` to +show a "Logout" link when not logged in. The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/authorization/ @@ -54,7 +49,7 @@ inside our ``models.py`` file. Add the following statements to your ``models.py`` file: .. literalinclude:: src/authorization/tutorial/models.py - :lines: 3-4,45-50 + :lines: 1-4,35-39 :linenos: :language: python @@ -92,14 +87,14 @@ We'll change our ``__init__.py`` file to enable an declarative security checking. We need to import the new policies: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 2-3,8 + :lines: 2-3,7 :linenos: :language: python Then, we'll add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 15-21 + :lines: 16-22 :linenos: :language: python @@ -111,49 +106,12 @@ represented by this policy: it is required. The ``callback`` is a ``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: 25,41-43 - :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. - -A ``logout`` :term:`view callable` will allow users to log out later: - -.. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 26,34 - :linenos: - :language: python - -We'll also add ``permission`` arguments with the value ``edit`` to the -``edit_page`` and ``add_page`` views. This indicates that the view -callables which these views 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: 37-40 - :linenos: - :language: python - -Adding these ``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: +When we're done configuring a root factory, adding a authentication and +authorization policies, and adding routes for ``/login`` and ``/logout``, +your application's ``__init__.py`` will look like this: .. literalinclude:: src/authorization/tutorial/__init__.py :linenos: @@ -191,30 +149,54 @@ views, the ``editor`` user should be able to add and edit pages. Adding Login and Logout Views ----------------------------- -We'll add a ``login`` view callable which renders a login form and -processes the post from the login form, checking credentials. +To our ``views.py`` we'll add a ``login`` view callable which renders a login +form and processes the post from the login form, checking credentials. We'll also add a ``logout`` view callable to our application and provide a link to it. This view will clear the credentials of the logged in user and redirect back to the front page. -We'll add a different file (for presentation convenience) to add login -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: +The ``login`` view callable will look something like this: -.. literalinclude:: src/authorization/tutorial/login.py +.. literalinclude:: src/authorization/tutorial/views.py + :pyobject: login :linenos: :language: python +The ``logout`` view callable will look something like this: + +.. literalinclude:: src/authorization/tutorial/views.py + :pyobject: logout + :linenos: + :language: python + +The ``login`` view callable is decorated with two ``@view_config`` +decorators, one which associates it with the ``login`` route, the other which +associates it with the ``HTTPForbidden`` context. The one which associates +it with the ``login`` route makes it visible when we visit ``/login``. The +one which associates it with the ``HTTPForbidden`` context makes it the +:term:`forbidden view`. The forbidden view is displayed whenever Pyramid or +your application raises an HTTPForbidden exception. In this case, we'll be +relying on the forbidden view to show the login form whenver someone attempts +to execute an action which they're not yet authorized to perform. + +The ``logout`` view callable is decorated with a ``@view_config`` decorator +which associates it with the ``logout`` route. This makes it visible when we +visit ``/login``. + +We'll need to import some stuff to service the needs of these two functions: +the ``HTTPForbidden`` exception, a number of values from the +``pyramid.security`` module, and a value from our newly added +``tutorial.security`` package. + 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`` view callables in ``views.py``. Within each of these views, +we'll need 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 :linenos: @@ -224,7 +206,6 @@ template. We'll add something like this to each view body: 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 :linenos: @@ -233,6 +214,28 @@ We'll then change the return value of these views to pass the `resulting logged_in = logged_in, edit_url = edit_url) +We'll also need to add a ``permission`` value to the ``@view_config`` +decorator for each of the ``add_page`` and ``edit_page`` view callables. For +each, we'll add ``permission='edit'``, for example: + +.. code-block:: python + :linenos: + + @view_config(route_name='edit_page', renderer='templates/edit.pt', + permission='edit') + +See the ``permission='edit'`` we added there? This indicates that the view +callables which these views reference cannot be invoked without the +authenticated user possessing the ``edit`` permission with respect to the +current :term:`context`. + +Adding these ``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. + Adding the ``login.pt`` Template -------------------------------- diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index 8dc886373..77658970d 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -2,82 +2,96 @@ Basic Layout ============ -The starter files generated by the ``pyramid_routesalchemy`` scaffold 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 ``alchemy`` scaffold are very 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/ <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/basiclayout/>`_. -App Startup with ``__init__.py`` --------------------------------- +Application Configuration with ``__init__.py`` +---------------------------------------------- A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file. Even if empty, this marks a directory as a Python -package. We use ``__init__.py`` both as a package marker and to contain -configuration code. +package. We use ``__init__.py`` both as a marker indicating the directory +it's contained within is a package, and to contain configuration code. Our +``__init__.py`` file will look like this: -The generated ``development.ini`` file is read by ``pserve`` which looks for -the application module in the ``use`` variable of the ``app:main`` -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 a function named -``main``. + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :linenos: + :language: py -First we need some imports to support later code: +Let's go over this piece-by-piece. First, we need some imports to support +later code: .. literalinclude:: src/basiclayout/tutorial/__init__.py :end-before: main :linenos: :language: py -Next we define the main function and create a SQLAlchemy database engine from -the ``sqlalchemy.`` prefixed settings in the ``development.ini`` file's -``[app:main]`` section. This will be a URI (something like -``sqlite://``): +``__init__.py`` defines a function named ``main``. Here is the entirety of +the ``main`` function we've defined in our ``__init__.py``: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :pyobject: main + :linenos: + :language: py + +When you invoke the ``pserve development.ini`` command, the ``main`` function +above is executed. It accepts some settings and returns a :term:`WSGI` +application. You can read :ref:`startup_chapter` for details about *how* +this function is found and called when you run ``pserve``, but for purposes +of brevity, we'll elide the details here. + +The main function first creates a SQLAlchemy database engine using +``engine_from_config`` from the ``sqlalchemy.`` prefixed settings in the +``development.ini`` file's ``[app:main]`` section. This will be a URI +(something like ``sqlite://``): .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 6-9 + :lines: 9 :linenos: :language: py -We then initialize our SQL database using SQLAlchemy, passing -it the engine: +``main`` then initializes 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`: +The next step of ``main`` is to construct a :term:`Configurator` object: .. 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 ``pyramid.reload_templates``, +dictionary values passed as the ``**settings`` argument. This will be a +dictionary of settings parsed from the ``.ini`` file, which contains +deployment-related values such as ``pyramid.reload_templates``, ``db_string``, etc. -We now can call :meth:`pyramid.config.Configurator.add_static_view` with the -arguments ``static`` (the name), and ``tutorial:static`` (the path): +``'main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with +two arguments: ``static`` (the name), and ``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. +This registers a static resource view which will match any URL that starts +with the prefix ``/static`` (by virtue of the first argument to add_static +view). This will serve up static resources for us from within the ``static`` +directory of our ``tutorial`` package, in this case, via +``http://localhost:6543/static/`` and below (by virtue of the second argument +to add_static_view). With this declaration, we're saying that any URL that +starts with ``/static`` should go to the static view; any remainder of its +path (e.g. the ``/foo`` in ``/static/foo``) will be used to compose a path to +a static file resource, such as a CSS file. -Using the configurator we can also register a :term:`route configuration` +Using the configurator ``main`` also registers a :term:`route configuration` via the :meth:`pyramid.config.Configurator.add_route` method that will be used when the URL is ``/``: @@ -88,44 +102,77 @@ used when the URL is ``/``: Since this route has a ``pattern`` equalling ``/`` it is the route that will be matched when the URL ``/`` is visted, e.g. ``http://localhost:6543/``. -Mapping the ``home`` route to code is done by registering a view. You will -use :meth:`pyramid.config.Configurator.add_view` in :term:`URL dispatch` to -register views for the routes, mapping your patterns to code: +``main`` next calls the ``scan`` method of the configurator, which will +recursively scan our ``tutorial`` package, looking for ``@view_config`` (and +other special) decorators. When it finds a ``@view_config`` decorator, a +view configuration will be registered, which will allow one of our +application URLs to be mapped to some code. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 14-15 + :lines: 14 :language: py -The first positional ``add_view`` argument ``tutorial.views.my_view`` is the -dotted name to a *function* we write (generated by the -``pyramid_routesalchemy`` scaffold) that is given a ``request`` object and -which returns a response or a dictionary. This view also names a -``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. - -Finally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app` -method to return a :term:`WSGI` application: +Finally, ``main`` is finished configuring things, so it uses the +:meth:`pyramid.config.Configurator.make_wsgi_app` method to return a +:term:`WSGI` application: .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 16 + :lines: 15 :language: py -Our final ``__init__.py`` file will look like this: +View Declarations via ``views.py`` +---------------------------------- - .. literalinclude:: src/basiclayout/tutorial/__init__.py +Mapping a :term:`route` to code that will be executed when that route's +pattern matches is done by registering a :term:`view configuration`. Our +application uses the :meth:`pyramid.view.view_config` decorator to map view +callables to each route, thereby mapping URL patterns to code. + +Here is the entirety of code in the ``views.py`` file within our package: + + .. literalinclude:: src/basiclayout/tutorial/views.py :linenos: :language: py +The important part to point out here is the ``@view_config`` decorator which +sits atop the ``my_view`` function. In fact, ``@view_config`` is so +important that we're going to ignore the rest of the code in the module at +this point just to explain it. The ``@view_config`` decorator associates the +function it decorates with a :term:`view configuration`. The view +configuration names a ``route_name`` (``home``), and names a ``renderer``, +which is a template which lives in the ``templates`` subdirectory of the +package. + +As the result of this view configuration, when the pattern associated with +the view named ``home`` is matched during a request, the function named +``my_view`` will be executed. The the function named ``my_view`` returns a +dictionary; the renderer will use the ``templates/mytemplate.pt`` template to +create a response based on the values in the dictionary. + +Note that the decorated function named ``my_view`` accepts a single argument +named ``request``. This is the standard call signature for a Pyramid +:term:`view callable`. + +Remember in our ``__init__.py`` when we executed the +:meth:`pyramid.config.Configurator.scan` method, e.g. ``config.scan()``? The +purpose of calling the scan method was to find and process this +``@view_config`` decorator in order to create a view configuration within our +application. Without being processed by ``scan``, the decorator effectively +does nothing. ``@view_config`` is inert without being detected via a +:term:`scan`. + Content Models with ``models.py`` --------------------------------- -In a SQLAlchemy-based application, a *model* object is an object -composed by querying the SQL database which backs an application. -SQLAlchemy is an "object relational mapper" (an ORM). The -``models.py`` file is where the ``pyramid_routesalchemy`` scaffold -put the classes that implement our models. +In a SQLAlchemy-based application, a *model* object is an object composed by +querying the SQL database. The ``models.py`` file is where the ``alchemy`` +scaffold put the classes that implement our models. + +Here is the complete source for ``models.py``: + + .. literalinclude:: src/basiclayout/tutorial/models.py + :linenos: + :language: py Let's take a look. First, we need some imports to support later code. @@ -137,7 +184,7 @@ Let's take a look. First, we need some imports to support later code. Next we set up a SQLAlchemy "DBSession" object: .. literalinclude:: src/basiclayout/tutorial/models.py - :lines: 15-16 + :lines: 16 :linenos: :language: py @@ -161,30 +208,6 @@ 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. -Next we define a function named ``populate`` which adds a single -model instance into our SQL storage and commits a transaction: - - .. 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 +That's about all there is to it to models, views, and initialization code in +our stock application. diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index 083ec0aa8..cd295e993 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -15,29 +15,25 @@ Making Edits to ``models.py`` .. 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. -The first thing we want to do is remove the stock ``MyModel`` class from the -generated ``models.py`` file. The ``MyModel`` class is only a sample and -we're not going to use it. - -Next, we'll remove the :class:`sqlalchemy.Unicode` import and replace it -with :class:`sqlalchemy.Text`. +Here's what our ``models.py`` file should look like after this step: .. literalinclude:: src/models/tutorial/models.py - :lines: 5 :linenos: :language: py -Then, we'll add a ``Page`` class. Because this is a SQLAlchemy -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. +The first thing we've done is to do is remove the stock ``MyModel`` class +from the generated ``models.py`` file. The ``MyModel`` class is only a +sample and we're not going to use it. + +Then, we added a ``Page`` class. Because this is a SQLAlchemy application, +this class inherits from an instance of +:class:`sqlalchemy.ext.declarative.declarative_base`. .. literalinclude:: src/models/tutorial/models.py :pyobject: Page @@ -54,24 +50,18 @@ 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. +Changing ``scripts/populate.py`` +-------------------------------- -.. literalinclude:: src/models/tutorial/models.py - :pyobject: initialize_sql - :linenos: - :language: python +We haven't looked at the guts of this file yet, but within the ``scripts`` +directory of your ``tutorial`` package is a file named ``populate.py``. Code +in this file is executed whenever we run the ``populate_tutorial`` command +(as we did in the installation step of this tutorial). -Here, we're using a slightly different binding syntax. It is otherwise -largely the same as the ``initialize_sql`` in the pcreate-generated -``models.py``. - -Our ``DBSession`` assignment stays the same as the original generated -``models.py``. - -Looking at the Result of all Our Edits to ``models.py`` -------------------------------------------------------- +Since we've changed our model, we need to make changes to our ``populate.py`` +script. In particular, we'll replace our import of ``MyModel`` with one of +``Page`` and we'll change the very end of the script to create a ``Page`` +rather than a ``MyModel`` and add it to our ``DBSession``. The result of all of our edits to ``models.py`` will end up looking something like this: @@ -80,6 +70,53 @@ something like this: :linenos: :language: python +Repopulating the Database +------------------------- + +Because our model has changed, in order to repopulate the database, we need +to rerun the ``populate_tutorial`` command to pick up the changes you've made +to both the models.py file and to the populate.py file. From the root of the +``tutorial`` project, directory execute the following commands. + +On UNIX: + +.. code-block:: text + + $ ../bin/populate_tutorial development.ini + +On Windows: + +.. code-block:: text + + c:\pyramidtut\tutorial> ..\Scripts\populate_tutorial development.ini + +Success will look something like this:: + + 2011-11-27 01:22:45,277 INFO [sqlalchemy.engine.base.Engine][MainThread] + PRAGMA table_info("pages") + 2011-11-27 01:22:45,277 INFO [sqlalchemy.engine.base.Engine][MainThread] () + 2011-11-27 01:22:45,277 INFO [sqlalchemy.engine.base.Engine][MainThread] + CREATE TABLE pages ( + id INTEGER NOT NULL, + name TEXT, + data TEXT, + PRIMARY KEY (id), + UNIQUE (name) + ) + + + 2011-11-27 01:22:45,278 INFO [sqlalchemy.engine.base.Engine][MainThread] () + 2011-11-27 01:22:45,397 INFO [sqlalchemy.engine.base.Engine][MainThread] + COMMIT + 2011-11-27 01:22:45,400 INFO [sqlalchemy.engine.base.Engine][MainThread] + BEGIN (implicit) + 2011-11-27 01:22:45,401 INFO [sqlalchemy.engine.base.Engine][MainThread] + INSERT INTO pages (name, data) VALUES (?, ?) + 2011-11-27 01:22:45,401 INFO [sqlalchemy.engine.base.Engine][MainThread] + ('FrontPage', 'This is the front page') + 2011-11-27 01:22:45,402 INFO [sqlalchemy.engine.base.Engine][MainThread] + COMMIT + Viewing the Application in a Browser ------------------------------------ diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index 21b97f7aa..7f533b635 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -2,34 +2,19 @@ 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. - -.. note:: - - A :app:`Pyramid` view can also be defined as callable - which accepts *two* arguments: a :term:`context` and a - :term:`request`. You'll see this two-argument pattern used in - other :app:`Pyramid` tutorials and applications. Either calling - convention will work in any :app:`Pyramid` application; the - calling conventions can be used interchangeably as necessary. In - :term:`url dispatch` based applications, however, the context - object is rarely used in the view body itself, so within this - tutorial we define views as callables that accept only a request to - avoid the visual "noise". If you do need the ``context`` within a - view function that only takes the request as a single argument, you - can obtain it via ``request.context``. - -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'``. +A :term:`view callable` in a :app:`Pyramid` application is typically a simple +Python function that accepts a single parameter named :term:`request`. A +view callable is assumed to return a :term:`response` object. + +The request object 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'``. The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/ @@ -52,24 +37,56 @@ Our resulting ``setup.py`` should look like so: :linenos: :language: python -.. note:: After these new dependencies are added, you will need to - rerun ``python setup.py develop`` inside the root of the - ``tutorial`` package to obtain and register the newly added - dependency package. +Running ``setup.py develop`` +============================ + +Since a new software dependency was added, you will need to rerun ``python +setup.py develop`` inside the root of the ``tutorial`` package to obtain and +register the newly added dependency distribution. + +Make sure your current working directory is the root of the project (the +directory in which setup.py lives) and execute the following command. + +On UNIX: + +.. code-block:: text + + $ cd tutorial + $ ../bin/python setup.py develop -Adding View Functions -===================== +On Windows: -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. +.. code-block:: text -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. + c:\pyramidtut> cd tutorial + c:\pyramidtut\tutorial> ..\Scripts\python setup.py develop + +Success executing this command will end with a line to the console something +like:: + + Finished processing dependencies for tutorial==0.0 + +Changing the ``views.py`` File +============================== + +We're going to edit our ``views.py`` in a rather major way. The result of +all of our edits to ``views.py`` will leave it looking like this: + +.. literalinclude:: src/views/tutorial/views.py + :linenos: + :language: python + +We've gotten rid of the ``my_view`` view function and its decorator that was +added when we originally rendered the ``alchemy`` scaffold. It was only an +example and isn't relevant to our application. + +Then we added 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. .. note:: @@ -195,16 +212,6 @@ If the view execution *is* a result of a form submission (if the expression attribute of the page object. It then redirects to the ``view_page`` view of the wiki page. -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: - -.. literalinclude:: src/views/tutorial/views.py - :linenos: - :language: python - Adding Templates ================ @@ -270,47 +277,41 @@ 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`` -======================================== +Adding Routes to ``__init__.py`` +================================ The ``__init__.py`` file contains -:meth:`pyramid.config.Configurator.add_view` calls which serve to map -routes via :term:`url dispatch` to views. First, we’ll get rid of the -existing route created by the template using the name ``'home'``. It’s only an -example and isn’t relevant to our application. +:meth:`pyramid.config.Configurator.add_route` calls which serve to add routes +to our application. First, we’ll get rid of the existing route created by +the template using the name ``'home'``. It’s only an example and isn’t +relevant to our application. We then need to add four calls to ``add_route``. Note that the *ordering* of these declarations is very important. ``route`` declarations are matched in the order they're found in the ``__init__.py`` file. #. Add a declaration which maps the pattern ``/`` (signifying the root URL) - to the route named ``view_wiki``. + to the route named ``view_wiki``. It maps to our ``view_wiki`` view + callable by virtue of the ``@view_config`` attached to the ``view_wiki`` + view function indicating ``route_name='view_wiki'``. #. Add a declaration which maps the pattern ``/{pagename}`` to the route named - ``view_page``. This is the regular view for a page. + ``view_page``. This is the regular view for a page. It maps + to our ``view_page`` view callable by virtue of the ``@view_config`` + attached to the ``view_page`` view function indicating + ``route_name='view_page'``. #. Add a declaration which maps the pattern ``/add_page/{pagename}`` to the - route named ``add_page``. This is the add view for a new page. + route named ``add_page``. This is the add view for a new page. It maps + to our ``add_page`` view callable by virtue of the ``@view_config`` + attached to the ``add_page`` view function indicating + ``route_name='add_page'``. #. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the - route named ``edit_page``. This is the edit view for a page. - -After we've defined the routes for our application, we can register views -to handle the processing and rendering that needs to happen when each route is -requested. - -#. Add a declaration which maps the ``view_wiki`` route to the view named - ``view_wiki`` in our ``views.py`` file. This is the :term:`default view` - for the wiki. - -#. Add a declaration which maps the ``view_page`` route to the view named - ``view_page`` in our ``views.py`` file. - -#. Add a declaration which maps the ``add_page`` route to the view named - ``add_page`` in our ``views.py`` file. - -#. Add a declaration which maps the ``edit_page`` route to the view named - ``edit_page`` in our ``views.py`` file. + route named ``edit_page``. This is the edit view for a page. It maps + to our ``edit_page`` view callable by virtue of the ``@view_config`` + attached to the ``edit_page`` view function indicating + ``route_name='edit_page'``. As a result of our edits, the ``__init__.py`` file should look something like so: diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index f9f5c4fba..be97f1cb3 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -40,13 +40,6 @@ Preparation, UNIX $ bin/easy_install pyramid -#. Use ``easy_install`` to install various packages from PyPI. - - .. code-block:: text - - $ bin/easy_install docutils nose coverage zope.sqlalchemy \ - SQLAlchemy pyramid_tm - Preparation, Windows -------------------- @@ -69,14 +62,6 @@ Preparation, Windows c:\pyramidtut> Scripts\easy_install pyramid -#. Use ``easy_install`` to install various packages from PyPI. - - .. code-block:: text - - c:\pyramidtut> Scripts\easy_install docutils \ - nose coverage zope.sqlalchemy SQLAlchemy pyramid_tm - - .. _sql_making_a_project: Making a Project @@ -84,7 +69,7 @@ Making a Project Your next step is to create a project. :app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We will use the -``pyramid_routesalchemy`` scaffold, which generates an application +``alchemy`` scaffold, which generates an application that uses :term:`SQLAlchemy` and :term:`URL dispatch`. The below instructions assume your current working directory is the @@ -94,20 +79,27 @@ On UNIX: .. code-block:: text - $ bin/pcreate -s routesalchemy tutorial + $ bin/pcreate -s alchemy tutorial On Windows: .. code-block:: text - c:\pyramidtut> Scripts\pcreate -s routesalchemy tutorial + c:\pyramidtut> Scripts\pcreate -s alchemy tutorial -.. note:: If you are using Windows, the ``pyramid_routesalchemy`` +.. note:: If you are using Windows, the ``alchemy`` scaffold may not deal gracefully with installation into a location that contains spaces in the path. If you experience startup problems, try putting both the virtualenv and the project into directories that do not contain spaces in their paths. +Success executing this command will end with a line to the console something +like:: + + Please run the "populate_tutorial" script to set up the SQL + database before starting the application (e.g. + "$myvirtualenv/bin/populate_tutorial development.ini".) + Installing the Project in "Development Mode" ============================================ @@ -131,6 +123,11 @@ On Windows: c:\pyramidtut> cd tutorial c:\pyramidtut\tutorial> ..\Scripts\python setup.py develop +Success executing this command will end with a line to the console something +like:: + + Finished processing dependencies for tutorial==0.0 + .. _sql_running_tests: Running the Tests @@ -151,22 +148,13 @@ On Windows: c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q -Starting the Application -======================== - -Start the application. +For a successful test run, you should see output like this:: -On UNIX: - -.. code-block:: text - - $ ../bin/pserve development.ini --reload - -On Windows: - -.. code-block:: text - - c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload + . + ---------------------------------------------------------------------- + Ran 1 test in 0.094s + + OK Exposing Test Coverage Information ================================== @@ -208,24 +196,142 @@ On Windows: c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^ --cover-erase --with-coverage -Looks like our package's ``models`` module doesn't quite have 100% -test coverage. +If successful, you will see output something like this:: -Visit the Application in a Browser -================================== + . + Name Stmts Miss Cover Missing + ------------------------------------------------ + tutorial 11 7 36% 9-15 + tutorial.models 17 0 100% + tutorial.scripts 0 0 100% + tutorial.tests 24 0 100% + tutorial.views 6 0 100% + ------------------------------------------------ + TOTAL 58 7 88% + ---------------------------------------------------------------------- + Ran 1 test in 0.459s + + OK + +Looks like our package doesn't quite have 100% test coverage. + +Starting the Application +======================== + +Start the application. + +On UNIX: + +.. code-block:: text + + $ ../bin/pserve development.ini --reload + +On Windows: + +.. code-block:: text + + c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload + +If successful, you will see something like this on your console:: + + Starting subprocess with file monitor + Starting server in PID 8966. + Starting HTTP server on http://0.0.0.0:6543 + +This means the server is ready to accept requests. + +Populating the Database +======================= + +In a web browser, visit ``http://localhost:6543/``. + +You will see an error page with a title something like this:: + + sqlalchemy.exc.OperationalError + + OperationalError: (OperationalError) no such table: models ... + +Oh no! Something isn't working! + +This happens because we haven't populated the SQL database with any table +information yet. We need to use the ``populate_tutorial`` :term:`console +script` to populate our database before we can see the page render correctly. + +Stop the running Pyramid application by pressing ``ctrl-C`` in the console. +Make sure you're still in the ``tutorial`` directory (the directory with a +``development.ini`` in it) and type the following command: + +On UNIX: + +.. code-block:: text + + $ ../bin/populate_tutorial development.ini + +On Windows: + +.. code-block:: text + + c:\pyramidtut\tutorial> ..\Scripts\populate_tutorial development.ini + +The output to your console should be something like this:: + + 2011-11-26 14:42:25,012 INFO [sqlalchemy.engine.base.Engine][MainThread] + PRAGMA table_info("models") + 2011-11-26 14:42:25,013 INFO [sqlalchemy.engine.base.Engine][MainThread] () + 2011-11-26 14:42:25,013 INFO [sqlalchemy.engine.base.Engine][MainThread] + CREATE TABLE models ( + id INTEGER NOT NULL, + name VARCHAR(255), + value INTEGER, + PRIMARY KEY (id), + UNIQUE (name) + ) + 2011-11-26 14:42:25,013 INFO [sqlalchemy.engine.base.Engine][MainThread] () + 2011-11-26 14:42:25,135 INFO [sqlalchemy.engine.base.Engine][MainThread] + COMMIT + 2011-11-26 14:42:25,137 INFO [sqlalchemy.engine.base.Engine][MainThread] + BEGIN (implicit) + 2011-11-26 14:42:25,138 INFO [sqlalchemy.engine.base.Engine][MainThread] + INSERT INTO models (name, value) VALUES (?, ?) + 2011-11-26 14:42:25,139 INFO [sqlalchemy.engine.base.Engine][MainThread] + (u'one', 1) + 2011-11-26 14:42:25,140 INFO [sqlalchemy.engine.base.Engine][MainThread] + COMMIT + +Success! You should now have a ``tutorial.db`` file in your current working +directory. This will be a SQLite database with a single table defined in it +(``models``). + +Starting the Application (Again) +================================ + +Start the application again. + +On UNIX: + +.. code-block:: text + + $ ../bin/pserve development.ini --reload + +On Windows: + +.. code-block:: text + + c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload -In a browser, visit ``http://localhost:6543/``. You will see the -generated application's default page. +At this point, when you visit ``http://localhost:6543/`` in your web browser, +you will no longer see an error; instead you will see the generated +application's default page. One thing you'll notice is the "debug toolbar" icon on right hand side of the page. You can read more about the purpose of the icon at :ref:`debug_toolbar`. It allows you to get information about your application while you develop. -Decisions the ``pyramid_routesalchemy`` Scaffold Has Made For You +Decisions the ``alchemy`` Scaffold Has Made For You ================================================================= -Creating a project using the ``pyramid_routesalchemy`` scaffold makes +Creating a project using the ``alchemy`` scaffold makes the following assumptions: - you are willing to use :term:`SQLAlchemy` as a database access tool diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/authorization/README.txt +++ b/docs/tutorials/wiki2/src/authorization/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/authorization/production.ini +++ b/docs/tutorials/wiki2/src/authorization/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py index 439a86923..09769bfff 100644 --- a/docs/tutorials/wiki2/src/authorization/setup.py +++ b/docs/tutorials/wiki2/src/authorization/setup.py @@ -42,6 +42,7 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) - diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py index cca52fdfe..04dd5fe82 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py @@ -4,14 +4,15 @@ from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql from tutorial.security import groupfinder +from .models import DBSession + def main(global_config, **settings): - """ This function returns a WSGI application. + """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder) authz_policy = ACLAuthorizationPolicy() @@ -19,27 +20,13 @@ def main(global_config, **settings): root_factory='tutorial.models.RootFactory', authentication_policy=authn_policy, authorization_policy=authz_policy) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('login', '/login') config.add_route('logout', '/logout') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') - - config.add_view('tutorial.views.view_wiki', route_name='view_wiki') - config.add_view('tutorial.login.login', route_name='login', - renderer='tutorial:templates/login.pt') - config.add_view('tutorial.login.logout', route_name='logout') - config.add_view('tutorial.views.view_page', route_name='view_page', - renderer='tutorial:templates/view.pt') - config.add_view('tutorial.views.add_page', route_name='add_page', - renderer='tutorial:templates/edit.pt', permission='edit') - config.add_view('tutorial.views.edit_page', route_name='edit_page', - renderer='tutorial:templates/edit.pt', permission='edit') - config.add_view('tutorial.login.login', - context='pyramid.httpexceptions.HTTPForbidden', - renderer='tutorial:templates/login.pt') + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/login.py b/docs/tutorials/wiki2/src/authorization/tutorial/login.py deleted file mode 100644 index 5a825d8d6..000000000 --- a/docs/tutorials/wiki2/src/authorization/tutorial/login.py +++ /dev/null @@ -1,37 +0,0 @@ -from pyramid.httpexceptions import HTTPFound -from pyramid.security import remember -from pyramid.security import forget - -from tutorial.security import USERS - -def login(request): - login_url = request.route_url('login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use the login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' - login = '' - password = '' - if 'form.submitted' in request.params: - login = request.params['login'] - password = request.params['password'] - if USERS.get(login) == password: - headers = remember(request, login) - return HTTPFound(location = came_from, - headers = headers) - message = 'Failed login' - - return dict( - message = message, - url = request.application_url + '/login', - came_from = came_from, - login = login, - password = password, - ) - -def logout(request): - headers = forget(request) - return HTTPFound(location = request.route_url('view_wiki'), - headers = headers) - diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py index 832545cb1..c3bdcbea5 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py @@ -1,17 +1,20 @@ -import transaction +from pyramid.security import ( + Allow, + Everyone, + ) -from pyramid.security import Allow -from pyramid.security import Everyone +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Text - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension @@ -29,20 +32,6 @@ class Page(Base): self.name = name self.data = data -def initialize_sql(engine): - DBSession.configure(bind=engine) - Base.metadata.bind = engine - Base.metadata.create_all(engine) - try: - transaction.begin() - session = DBSession() - page = Page('FrontPage', 'This is the front page') - session.add(page) - transaction.commit() - except IntegrityError: - # already created - transaction.abort() - class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/populate.py new file mode 100644 index 000000000..981adff38 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/populate.py @@ -0,0 +1,35 @@ +import os +import sys +import transaction + +from sqlalchemy import engine_from_config + +from pyramid.paster import ( + get_appsettings, + setup_logging, + ) + +from ..models import ( + DBSession, + Page, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv, settings=None): + if len(argv) != 2: + usage(argv) + config_uri = argv[1] + setup_logging(config_uri) + settings = get_appsettings(config_uri) + engine = engine_from_config(settings, 'sqlalchemy.') + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/security.py b/docs/tutorials/wiki2/src/authorization/tutorial/security.py index cfd13071e..d88c9c71f 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/security.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/security.py @@ -5,4 +5,3 @@ GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) - diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py index 332031ba4..31d2dc6d5 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py @@ -1,15 +1,20 @@ import unittest - +import transaction from pyramid import testing def _initTestingDB(): - from tutorial.models import DBSession - from tutorial.models import Base from sqlalchemy import create_engine + from tutorial.models import ( + DBSession, + Page, + Base + ) engine = create_engine('sqlite://') - DBSession.configure(bind=engine) - Base.metadata.bind = engine Base.metadata.create_all(engine) + DBSession.configure(bind=engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) return DBSession def _registerRoutes(config): @@ -20,14 +25,16 @@ def _registerRoutes(config): class ViewWikiTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() + self.session = _initTestingDB() def tearDown(self): + self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import view_wiki return view_wiki(request) - + def test_it(self): _registerRoutes(self.config) request = testing.DummyRequest() @@ -40,6 +47,7 @@ class ViewPageTests(unittest.TestCase): self.config = testing.setUp() def tearDown(self): + self.session.remove() testing.tearDown() def _callFUT(self, request): @@ -121,7 +129,8 @@ class EditPageTests(unittest.TestCase): self.session.add(page) info = self._callFUT(request) self.assertEqual(info['page'], page) - self.assertEqual(info['save_url'], 'http://example.com/abc/edit_page') + self.assertEqual(info['save_url'], + 'http://example.com/abc/edit_page') def test_it_submitted(self): from tutorial.models import Page diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py index fc85d4585..375f1f5a5 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py @@ -1,20 +1,36 @@ import re - from docutils.core import publish_parts -from pyramid.httpexceptions import HTTPFound, HTTPNotFound -from pyramid.security import authenticated_userid +from pyramid.httpexceptions import ( + HTTPFound, + HTTPNotFound, + HTTPForbidden, + ) + +from pyramid.view import view_config + +from pyramid.security import ( + remember, + forget, + authenticated_userid, + ) -from tutorial.models import DBSession -from tutorial.models import Page +from .models import ( + DBSession, + Page, + ) + +from .security import USERS # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") +@view_config(route_name='view_wiki') def view_wiki(request): return HTTPFound(location = request.route_url('view_page', pagename='FrontPage')) +@view_config(route_name='view_page', renderer='templates/view.pt') def view_page(request): pagename = request.matchdict['pagename'] session = DBSession() @@ -35,10 +51,11 @@ def view_page(request): content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.route_url('edit_page', pagename=pagename) - logged_in = authenticated_userid(request) return dict(page=page, content=content, edit_url=edit_url, - logged_in=logged_in) + logged_in=authenticated_userid(request)) +@view_config(route_name='add_page', renderer='templates/edit.pt', + permission='edit') def add_page(request): name = request.matchdict['pagename'] if 'form.submitted' in request.params: @@ -50,9 +67,11 @@ def add_page(request): pagename=name)) save_url = request.route_url('add_page', pagename=name) page = Page('', '') - logged_in = authenticated_userid(request) - return dict(page=page, save_url=save_url, logged_in=logged_in) + return dict(page=page, save_url=save_url, + logged_in=authenticated_userid(request)) +@view_config(route_name='edit_page', renderer='templates/edit.pt', + permission='edit') def edit_page(request): name = request.matchdict['pagename'] session = DBSession() @@ -62,10 +81,43 @@ def edit_page(request): session.add(page) return HTTPFound(location = request.route_url('view_page', pagename=name)) - - logged_in = authenticated_userid(request) return dict( page=page, save_url = request.route_url('edit_page', pagename=name), - logged_in = logged_in, + logged_in=authenticated_userid(request), ) + +@view_config(route_name='login', renderer='templates/login.pt') +@view_config(context=HTTPForbidden, renderer='templates/login.pt') +def login(request): + login_url = request.route_url('login') + referrer = request.url + if referrer == login_url: + referrer = '/' # never use the login form itself as came_from + came_from = request.params.get('came_from', referrer) + message = '' + login = '' + password = '' + if 'form.submitted' in request.params: + login = request.params['login'] + password = request.params['password'] + if USERS.get(login) == password: + headers = remember(request, login) + return HTTPFound(location = came_from, + headers = headers) + message = 'Failed login' + + return dict( + message = message, + url = request.application_url + '/login', + came_from = came_from, + login = login, + password = password, + ) + +@view_config(route_name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location = request.route_url('view_wiki'), + headers = headers) + diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/basiclayout/README.txt +++ b/docs/tutorials/wiki2/src/basiclayout/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/basiclayout/production.ini +++ b/docs/tutorials/wiki2/src/basiclayout/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py index 3ab493912..0ca918cab 100644 --- a/docs/tutorials/wiki2/src/basiclayout/setup.py +++ b/docs/tutorials/wiki2/src/basiclayout/setup.py @@ -41,6 +41,8 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py index b4038de3c..253341563 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py @@ -1,18 +1,16 @@ from pyramid.config import Configurator from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql +from .models import DBSession def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) config = Configurator(settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') - config.add_view('tutorial.views.my_view', route_name='home', - renderer='templates/mytemplate.pt') + config.scan() return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py index 9b687931b..b6ac15429 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py @@ -1,43 +1,28 @@ -import transaction +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Unicode - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension -DBSession = scoped_session(sessionmaker( - extension=ZopeTransactionExtension())) +DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class MyModel(Base): __tablename__ = 'models' id = Column(Integer, primary_key=True) - name = Column(Unicode(255), unique=True) + name = Column(Text, unique=True) value = Column(Integer) def __init__(self, name, value): self.name = name self.value = value -def populate(): - session = DBSession() - model = MyModel(name=u'root',value=55) - session.add(model) - session.flush() - transaction.commit() - -def initialize_sql(engine): - DBSession.configure(bind=engine) - Base.metadata.bind = engine - Base.metadata.create_all(engine) - try: - populate() - except IntegrityError: - transaction.abort() diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/populate.py new file mode 100644 index 000000000..0e828465f --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/populate.py @@ -0,0 +1,35 @@ +import os +import sys +import transaction + +from sqlalchemy import engine_from_config + +from pyramid.paster import ( + get_appsettings, + setup_logging, + ) + +from ..models import ( + DBSession, + MyModel, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv): + if len(argv) != 2: + usage(argv) + config_uri = argv[1] + setup_logging(config_uri) + settings = get_appsettings(config_uri) + engine = engine_from_config(settings, 'sqlalchemy.') + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = MyModel(name='one', value=1) + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt index 14b88d16a..fbfa9870b 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt @@ -6,9 +6,9 @@ <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://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <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]--> diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py index 5efa6affa..653d061e4 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py @@ -1,23 +1,32 @@ import unittest +import transaction + from pyramid import testing -def _initTestingDB(): - from sqlalchemy import create_engine - from tutorial.models import initialize_sql - session = initialize_sql(create_engine('sqlite://')) - return session +from .models import DBSession class TestMyView(unittest.TestCase): def setUp(self): self.config = testing.setUp() - _initTestingDB() + from sqlalchemy import create_engine + engine = create_engine('sqlite://') + from .models import ( + Base, + MyModel, + ) + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = MyModel(name='one', value=55) + DBSession.add(model) def tearDown(self): + DBSession.remove() testing.tearDown() def test_it(self): - from tutorial.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['root'].name, 'root') + self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'tutorial') diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py index e550e3257..3e6abf2c2 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py @@ -1,7 +1,11 @@ -from tutorial.models import DBSession -from tutorial.models import MyModel +from pyramid.view import view_config +from .models import ( + DBSession, + MyModel, + ) + +@view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): - dbsession = DBSession() - root = dbsession.query(MyModel).filter(MyModel.name==u'root').first() - return {'root':root, 'project':'tutorial'} + one = DBSession.query(MyModel).filter(MyModel.name=='one').first() + return {'one':one, 'project':'tutorial'} diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/models/README.txt +++ b/docs/tutorials/wiki2/src/models/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py index 3ab493912..0ca918cab 100644 --- a/docs/tutorials/wiki2/src/models/setup.py +++ b/docs/tutorials/wiki2/src/models/setup.py @@ -41,6 +41,8 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index fda7c9ce6..253341563 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -1,16 +1,16 @@ from pyramid.config import Configurator from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql +from .models import DBSession def main(global_config, **settings): - """ This function returns a WSGI application. + """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) config = Configurator(settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') - config.add_view('tutorial.views.my_view', route_name='home', - renderer='templates/mytemplate.pt') + config.scan() return config.make_wsgi_app() + diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py index 30f77a0b9..499396c5b 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models.py @@ -1,19 +1,19 @@ -import transaction +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Text - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension -DBSession = scoped_session(sessionmaker( - extension=ZopeTransactionExtension())) +DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class Page(Base): @@ -27,16 +27,3 @@ class Page(Base): self.name = name self.data = data -def initialize_sql(engine): - DBSession.configure(bind=engine) - Base.metadata.bind = engine - Base.metadata.create_all(engine) - try: - transaction.begin() - session = DBSession() - page = Page('FrontPage', 'This is the front page') - session.add(page) - transaction.commit() - except IntegrityError: - # already created - transaction.abort() diff --git a/docs/tutorials/wiki2/src/models/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/models/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/models/tutorial/scripts/populate.py new file mode 100644 index 000000000..03188e8ad --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/scripts/populate.py @@ -0,0 +1,35 @@ +import os +import sys +import transaction + +from sqlalchemy import engine_from_config + +from pyramid.paster import ( + get_appsettings, + setup_logging, + ) + +from ..models import ( + DBSession, + Page, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv): + if len(argv) != 2: + usage(argv) + config_uri = argv[1] + setup_logging(config_uri) + settings = get_appsettings(config_uri) + engine = engine_from_config(settings, 'sqlalchemy.') + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt index 14b88d16a..fbfa9870b 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt @@ -6,9 +6,9 @@ <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://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <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]--> diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py index 71f5e21e3..653d061e4 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py @@ -1,22 +1,32 @@ import unittest +import transaction + from pyramid import testing -def _initTestingDB(): - from tutorial.models import initialize_sql - session = initialize_sql('sqlite://') - return session +from .models import DBSession class TestMyView(unittest.TestCase): def setUp(self): self.config = testing.setUp() - _initTestingDB() + from sqlalchemy import create_engine + engine = create_engine('sqlite://') + from .models import ( + Base, + MyModel, + ) + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = MyModel(name='one', value=55) + DBSession.add(model) def tearDown(self): + DBSession.remove() testing.tearDown() def test_it(self): - from tutorial.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['root'].name, 'root') + self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'tutorial') diff --git a/docs/tutorials/wiki2/src/models/tutorial/views.py b/docs/tutorials/wiki2/src/models/tutorial/views.py index e550e3257..3e6abf2c2 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/views.py +++ b/docs/tutorials/wiki2/src/models/tutorial/views.py @@ -1,7 +1,11 @@ -from tutorial.models import DBSession -from tutorial.models import MyModel +from pyramid.view import view_config +from .models import ( + DBSession, + MyModel, + ) + +@view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): - dbsession = DBSession() - root = dbsession.query(MyModel).filter(MyModel.name==u'root').first() - return {'root':root, 'project':'tutorial'} + one = DBSession.query(MyModel).filter(MyModel.name=='one').first() + return {'one':one, 'project':'tutorial'} diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/tests/README.txt +++ b/docs/tutorials/wiki2/src/tests/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/tests/development.ini +++ b/docs/tutorials/wiki2/src/tests/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/tests/production.ini +++ b/docs/tutorials/wiki2/src/tests/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py index 6de8a1fbe..f965ccc6e 100644 --- a/docs/tutorials/wiki2/src/tests/setup.py +++ b/docs/tutorials/wiki2/src/tests/setup.py @@ -43,6 +43,7 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) - diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py index cca52fdfe..04dd5fe82 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py @@ -4,14 +4,15 @@ from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql from tutorial.security import groupfinder +from .models import DBSession + def main(global_config, **settings): - """ This function returns a WSGI application. + """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder) authz_policy = ACLAuthorizationPolicy() @@ -19,27 +20,13 @@ def main(global_config, **settings): root_factory='tutorial.models.RootFactory', authentication_policy=authn_policy, authorization_policy=authz_policy) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('login', '/login') config.add_route('logout', '/logout') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') - - config.add_view('tutorial.views.view_wiki', route_name='view_wiki') - config.add_view('tutorial.login.login', route_name='login', - renderer='tutorial:templates/login.pt') - config.add_view('tutorial.login.logout', route_name='logout') - config.add_view('tutorial.views.view_page', route_name='view_page', - renderer='tutorial:templates/view.pt') - config.add_view('tutorial.views.add_page', route_name='add_page', - renderer='tutorial:templates/edit.pt', permission='edit') - config.add_view('tutorial.views.edit_page', route_name='edit_page', - renderer='tutorial:templates/edit.pt', permission='edit') - config.add_view('tutorial.login.login', - context='pyramid.httpexceptions.HTTPForbidden', - renderer='tutorial:templates/login.pt') + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/tests/tutorial/login.py b/docs/tutorials/wiki2/src/tests/tutorial/login.py deleted file mode 100644 index 5a825d8d6..000000000 --- a/docs/tutorials/wiki2/src/tests/tutorial/login.py +++ /dev/null @@ -1,37 +0,0 @@ -from pyramid.httpexceptions import HTTPFound -from pyramid.security import remember -from pyramid.security import forget - -from tutorial.security import USERS - -def login(request): - login_url = request.route_url('login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use the login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' - login = '' - password = '' - if 'form.submitted' in request.params: - login = request.params['login'] - password = request.params['password'] - if USERS.get(login) == password: - headers = remember(request, login) - return HTTPFound(location = came_from, - headers = headers) - message = 'Failed login' - - return dict( - message = message, - url = request.application_url + '/login', - came_from = came_from, - login = login, - password = password, - ) - -def logout(request): - headers = forget(request) - return HTTPFound(location = request.route_url('view_wiki'), - headers = headers) - diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models.py b/docs/tutorials/wiki2/src/tests/tutorial/models.py index 832545cb1..c3bdcbea5 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/models.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/models.py @@ -1,17 +1,20 @@ -import transaction +from pyramid.security import ( + Allow, + Everyone, + ) -from pyramid.security import Allow -from pyramid.security import Everyone +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Text - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension @@ -29,20 +32,6 @@ class Page(Base): self.name = name self.data = data -def initialize_sql(engine): - DBSession.configure(bind=engine) - Base.metadata.bind = engine - Base.metadata.create_all(engine) - try: - transaction.begin() - session = DBSession() - page = Page('FrontPage', 'This is the front page') - session.add(page) - transaction.commit() - except IntegrityError: - # already created - transaction.abort() - class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] diff --git a/docs/tutorials/wiki2/src/tests/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/tests/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/tests/tutorial/scripts/populate.py new file mode 100644 index 000000000..de74f4d63 --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/tutorial/scripts/populate.py @@ -0,0 +1,36 @@ +import os +import sys +import transaction + +from sqlalchemy import engine_from_config + +from pyramid.paster import ( + get_appsettings, + setup_logging, + ) + +from ..models import ( + DBSession, + Page, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv, settings=None): + if len(argv) != 2: + usage(argv) + config_uri = argv[1] + if settings is None: + setup_logging(config_uri) + settings = get_appsettings(config_uri) + engine = engine_from_config(settings, 'sqlalchemy.') + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security.py b/docs/tutorials/wiki2/src/tests/tutorial/security.py index cfd13071e..d88c9c71f 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/security.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/security.py @@ -5,4 +5,3 @@ GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) - diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests.py b/docs/tutorials/wiki2/src/tests/tutorial/tests.py index 8439e2748..557d1b1be 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/tests.py @@ -1,16 +1,20 @@ import unittest - +import transaction from pyramid import testing - def _initTestingDB(): - from tutorial.models import DBSession - from tutorial.models import Base from sqlalchemy import create_engine - engine = create_engine('sqlite:///:memory:') - DBSession.configure(bind=engine) - Base.metadata.bind = engine + from tutorial.models import ( + DBSession, + Page, + Base + ) + engine = create_engine('sqlite://') Base.metadata.create_all(engine) + DBSession.configure(bind=engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) return DBSession def _registerRoutes(config): @@ -39,28 +43,6 @@ class PageModelTests(unittest.TestCase): self.assertEqual(instance.name, 'SomeName') self.assertEqual(instance.data, 'some data') -class InitializeSqlTests(unittest.TestCase): - - def setUp(self): - from tutorial.models import DBSession - DBSession.remove() - - def tearDown(self): - from tutorial.models import DBSession - DBSession.remove() - - def _callFUT(self, engine): - from tutorial.models import initialize_sql - return initialize_sql(engine) - - def test_it(self): - from sqlalchemy import create_engine - engine = create_engine('sqlite:///:memory:') - self._callFUT(engine) - from tutorial.models import DBSession, Page - self.assertEqual(DBSession.query(Page).one().data, - 'This is the front page') - class ViewWikiTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() @@ -191,10 +173,11 @@ class FunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main - settings = { 'sqlalchemy.url': 'sqlite:///:memory:'} + settings = { 'sqlalchemy.url': 'sqlite://'} app = main({}, **settings) from webtest import TestApp self.testapp = TestApp(app) + _initTestingDB() def tearDown(self): del self.testapp @@ -263,3 +246,23 @@ class FunctionalTests(unittest.TestCase): self.testapp.get(self.editor_login, status=302) res = self.testapp.get('/FrontPage', status=200) self.assertTrue('FrontPage' in res.body) + +class Test_populate(unittest.TestCase): + def setUp(self): + from tutorial.models import DBSession + DBSession.remove() + + def tearDown(self): + from tutorial.models import DBSession + DBSession.remove() + + def _callFUT(self, settings): + from tutorial.scripts.populate import main + main(['foo', 'development.ini'], settings) + + def test_it(self): + self._callFUT({'sqlalchemy.url':'sqlite://'}) + from tutorial.models import DBSession, Page + self.assertEqual(DBSession.query(Page).one().data, + 'This is the front page') + diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py index fc85d4585..375f1f5a5 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/views.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/views.py @@ -1,20 +1,36 @@ import re - from docutils.core import publish_parts -from pyramid.httpexceptions import HTTPFound, HTTPNotFound -from pyramid.security import authenticated_userid +from pyramid.httpexceptions import ( + HTTPFound, + HTTPNotFound, + HTTPForbidden, + ) + +from pyramid.view import view_config + +from pyramid.security import ( + remember, + forget, + authenticated_userid, + ) -from tutorial.models import DBSession -from tutorial.models import Page +from .models import ( + DBSession, + Page, + ) + +from .security import USERS # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") +@view_config(route_name='view_wiki') def view_wiki(request): return HTTPFound(location = request.route_url('view_page', pagename='FrontPage')) +@view_config(route_name='view_page', renderer='templates/view.pt') def view_page(request): pagename = request.matchdict['pagename'] session = DBSession() @@ -35,10 +51,11 @@ def view_page(request): content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.route_url('edit_page', pagename=pagename) - logged_in = authenticated_userid(request) return dict(page=page, content=content, edit_url=edit_url, - logged_in=logged_in) + logged_in=authenticated_userid(request)) +@view_config(route_name='add_page', renderer='templates/edit.pt', + permission='edit') def add_page(request): name = request.matchdict['pagename'] if 'form.submitted' in request.params: @@ -50,9 +67,11 @@ def add_page(request): pagename=name)) save_url = request.route_url('add_page', pagename=name) page = Page('', '') - logged_in = authenticated_userid(request) - return dict(page=page, save_url=save_url, logged_in=logged_in) + return dict(page=page, save_url=save_url, + logged_in=authenticated_userid(request)) +@view_config(route_name='edit_page', renderer='templates/edit.pt', + permission='edit') def edit_page(request): name = request.matchdict['pagename'] session = DBSession() @@ -62,10 +81,43 @@ def edit_page(request): session.add(page) return HTTPFound(location = request.route_url('view_page', pagename=name)) - - logged_in = authenticated_userid(request) return dict( page=page, save_url = request.route_url('edit_page', pagename=name), - logged_in = logged_in, + logged_in=authenticated_userid(request), ) + +@view_config(route_name='login', renderer='templates/login.pt') +@view_config(context=HTTPForbidden, renderer='templates/login.pt') +def login(request): + login_url = request.route_url('login') + referrer = request.url + if referrer == login_url: + referrer = '/' # never use the login form itself as came_from + came_from = request.params.get('came_from', referrer) + message = '' + login = '' + password = '' + if 'form.submitted' in request.params: + login = request.params['login'] + password = request.params['password'] + if USERS.get(login) == password: + headers = remember(request, login) + return HTTPFound(location = came_from, + headers = headers) + message = 'Failed login' + + return dict( + message = message, + url = request.application_url + '/login', + came_from = came_from, + login = login, + password = password, + ) + +@view_config(route_name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location = request.route_url('view_wiki'), + headers = headers) + diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/views/README.txt +++ b/docs/tutorials/wiki2/src/views/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/views/production.ini +++ b/docs/tutorials/wiki2/src/views/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py index 439a86923..9c0e88eb0 100644 --- a/docs/tutorials/wiki2/src/views/setup.py +++ b/docs/tutorials/wiki2/src/views/setup.py @@ -42,6 +42,8 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py index 7d79f7a1f..b30d593cf 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py @@ -1,25 +1,18 @@ from pyramid.config import Configurator from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql +from .models import DBSession def main(global_config, **settings): - """ This function returns a WSGI application. + """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) config = Configurator(settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') - config.add_view('tutorial.views.view_wiki', route_name='view_wiki') - config.add_view('tutorial.views.view_page', route_name='view_page', - renderer='tutorial:templates/view.pt') - config.add_view('tutorial.views.add_page', route_name='add_page', - renderer='tutorial:templates/edit.pt') - config.add_view('tutorial.views.edit_page', route_name='edit_page', - renderer='tutorial:templates/edit.pt') + config.scan() return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py index 30506f67e..499396c5b 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models.py @@ -1,14 +1,15 @@ -import transaction +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Text - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension @@ -26,16 +27,3 @@ class Page(Base): self.name = name self.data = data -def initialize_sql(engine): - DBSession.configure(bind=engine) - Base.metadata.bind = engine - Base.metadata.create_all(engine) - try: - transaction.begin() - session = DBSession() - page = Page('FrontPage', 'initial data') - session.add(page) - transaction.commit() - except IntegrityError: - # already created - transaction.abort() diff --git a/docs/tutorials/wiki2/src/views/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/views/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/views/tutorial/scripts/populate.py new file mode 100644 index 000000000..03188e8ad --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/scripts/populate.py @@ -0,0 +1,35 @@ +import os +import sys +import transaction + +from sqlalchemy import engine_from_config + +from pyramid.paster import ( + get_appsettings, + setup_logging, + ) + +from ..models import ( + DBSession, + Page, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv): + if len(argv) != 2: + usage(argv) + config_uri = argv[1] + setup_logging(config_uri) + settings = get_appsettings(config_uri) + engine = engine_from_config(settings, 'sqlalchemy.') + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt index 14b88d16a..fbfa9870b 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt @@ -6,9 +6,9 @@ <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://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <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]--> diff --git a/docs/tutorials/wiki2/src/views/tutorial/tests.py b/docs/tutorials/wiki2/src/views/tutorial/tests.py index 668bf5479..31d2dc6d5 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/views/tutorial/tests.py @@ -1,15 +1,20 @@ import unittest - +import transaction from pyramid import testing def _initTestingDB(): - from tutorial.models import DBSession - from tutorial.models import Base from sqlalchemy import create_engine + from tutorial.models import ( + DBSession, + Page, + Base + ) engine = create_engine('sqlite://') - DBSession.configure(bind=engine) - Base.metadata.bind = engine Base.metadata.create_all(engine) + DBSession.configure(bind=engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) return DBSession def _registerRoutes(config): @@ -20,8 +25,10 @@ def _registerRoutes(config): class ViewWikiTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() + self.session = _initTestingDB() def tearDown(self): + self.session.remove() testing.tearDown() def _callFUT(self, request): diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py index e04b96ae4..5c49dd2e8 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/views.py +++ b/docs/tutorials/wiki2/src/views/tutorial/views.py @@ -1,19 +1,26 @@ import re - from docutils.core import publish_parts -from pyramid.httpexceptions import HTTPFound, HTTPNotFound +from pyramid.httpexceptions import ( + HTTPFound, + HTTPNotFound, + ) +from pyramid.view import view_config -from tutorial.models import DBSession -from tutorial.models import Page +from .models import ( + DBSession, + Page, + ) # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") +@view_config(route_name='view_wiki') def view_wiki(request): return HTTPFound(location = request.route_url('view_page', pagename='FrontPage')) +@view_config(route_name='view_page', renderer='templates/view.pt') def view_page(request): pagename = request.matchdict['pagename'] session = DBSession() @@ -36,6 +43,7 @@ def view_page(request): edit_url = request.route_url('edit_page', pagename=pagename) return dict(page=page, content=content, edit_url=edit_url) +@view_config(route_name='add_page', renderer='templates/edit.pt') def add_page(request): name = request.matchdict['pagename'] if 'form.submitted' in request.params: @@ -49,6 +57,7 @@ def add_page(request): page = Page('', '') return dict(page=page, save_url=save_url) +@view_config(route_name='edit_page', renderer='templates/edit.pt') def edit_page(request): name = request.matchdict['pagename'] session = DBSession() diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst index c120b1c61..25cf2c25e 100644 --- a/docs/tutorials/wiki2/tests.rst +++ b/docs/tutorials/wiki2/tests.rst @@ -13,9 +13,9 @@ We write a test class for the model class ``Page`` and another test class for the ``initialize_sql`` function. To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a -result of the ``pyramid_routesalchemy`` project generator. We'll add two -test classes: one for the ``Page`` model named ``PageModelTests``, one for the -``initialize_sql`` function named ``InitializeSqlTests``. +result of the ``alchemy`` scaffold. We'll add two test classes: one for the +``Page`` model named ``PageModelTests``, and one for the ``populate`` script +named ``Test_populate``. Testing the Views ================= diff --git a/docs/whatsnew-1.0.rst b/docs/whatsnew-1.0.rst index 61d74a899..66cb9be3a 100644 --- a/docs/whatsnew-1.0.rst +++ b/docs/whatsnew-1.0.rst @@ -110,7 +110,7 @@ Scaffold Improvements (``starter``, ``routesalchemy``, ``alchemy``, ``zodb``) instead of ZCML configuration. -- The ``pyramid_zodb``, ``pyramid_routesalchemy`` and ``pyramid_alchemy`` +- The ``pyramid_zodb``, ``routesalchemy`` and ``pyramid_alchemy`` scaffolds 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 diff --git a/docs/whatsnew-1.2.rst b/docs/whatsnew-1.2.rst index 49854a7e7..ea56cf52d 100644 --- a/docs/whatsnew-1.2.rst +++ b/docs/whatsnew-1.2.rst @@ -63,7 +63,7 @@ Scaffolding Changes package at all; configuration in the ``production.ini`` file which used to require its ``error_catcher`` middleware has been removed. Configuring error catching / email sending is now the domain of the ``pyramid_exclog`` - package (see https://docs.pylonsproject.org/projects/pyramid_exclog/dev/). + package (see http://docs.pylonsproject.org/projects/pyramid_exclog/dev/). - All scaffolds now send the ``cache_max_age`` parameter to the ``add_static_view`` method. diff --git a/docs/whatsnew-1.3.rst b/docs/whatsnew-1.3.rst new file mode 100644 index 000000000..28c161ad0 --- /dev/null +++ b/docs/whatsnew-1.3.rst @@ -0,0 +1,315 @@ +What's New In Pyramid 1.3 +========================= + +This article explains the new features in :app:`Pyramid` version 1.3 as +compared to its predecessor, :app:`Pyramid` 1.2. It also documents backwards +incompatibilities between the two versions and deprecations added to +:app:`Pyramid` 1.3, as well as software dependency changes and notable +documentation additions. + +Major Feature Additions +----------------------- + +The major feature additions in Pyramid 1.3 follow. + +Python 3 Compatibility +~~~~~~~~~~~~~~~~~~~~~~ + +Pyramid is now Python 3 compatible. Python 3.2 or better is required. + +.. warning:: + + As of this writing (the release of Pyramid 1.3a1), if you attempt to + install a Pyramid project that used ``alchemy`` scaffold via ``setup.py + develop`` on Python 3.2, it may quit with an installation error while + trying to install ``Pygments``. If this happens, please rerun the + ``setup.py develop`` command again and it will complete. We're just as + clueless as you are as to why this happens at this point, but hopefully + we'll figure it out before Pyramid 1.3 leaves the alpha/beta phase. + +This feature required us to make some compromises. + +Pyramid no longer runs on Python 2.5. This includes the most recent release +of Jython and the Python 2.5 version of Google App Engine. We could not +easily "straddle" Python 2 and 3 versions and support Python 2 versions older +than Python 2.6. You will need Python 2.6 or better to run this version of +Pyramid. If you need to use Python 2.5, you should use the most recent 1.2.X +release of Pyramid. + +Though many Pyramid add-ons have releases which are already Python 3 +compatible (in particular ``pyramid_debugtoolbar``, ``pyramid_jinja2``, +``pyramid_exclog``, and ``pyramid_tm``), some are still known to work only +under Python 2. Likewise, some scaffolding dependencies (particularly ZODB) +do not yet work under Python +3. Please be patient as we gain full ecosystem support for Python 3. You +can see more details about ongoing porting efforts at +https://github.com/Pylons/pyramid/wiki/Python-3-Porting . + +The libraries named ``Paste`` and ``PasteScript`` which have been +dependencies of Pyramid since 1.0+ have not been ported to Python 3, and we +were unwilling to port and maintain them ourselves. As a result, we've had +to make some changes: + +- We've replaced the ``paster`` command with Pyramid-specific analogues. + +- We've made the default WSGI server the ``wsgiref`` server. + +Previously (in Pyramid 1.0, 1.1 and 1.2), you created a Pyramid application +using ``paster create``, like so:: + + $ myvenv/bin/paster create -t pyramid_starter foo + +You're now instead required to create an application using ``pcreate`` like +so:: + + $ myvenv/bin/pcreate -s starter foo + +Note that the names of available scaffolds have changed and the flags +supported by ``pcreate`` are different than those that were supported by +``paster create``. + +Instead of running a Pyramid project created via a scaffold using ``paster +serve``, as was done in Pyramid <= 1.2.X, you now must use the ``pserve`` +command:: + + $myvenv/bin/pserve development.ini + +The ``ini`` configuration file format supported by Pyramid has not changed. +As a result, Python 2-only users can install PasteScript manually and use +``paster serve`` and ``paster create`` instead if they like. However, using +``pserve`` and ``pcreate`` will work under both Python 2 and Python 3. + +Analogues of ``paster pshell``, ``paster pviews`` and ``paster ptweens`` also +exist under the respective console script names ``pshell``, ``pviews``, and +``ptweens``. + +We've replaced use of the Paste ``httpserver`` with the ``wsgiref`` server in +the scaffolds, so once you create a project from a scaffold, its +``development.ini`` and ``production.ini`` will have the following line:: + + use = egg:pyramid#wsgiref + +Instead of this (which was the default in older versions):: + + use = egg:Paste#http + +Using ``wsgiref`` as the default WSGI server is purely a default to make it +possible to use the same scaffolding under Python 2 and Python 3; people +running Pyramid under Python 2 can still manually install ``Paste`` and use +the Paste ``httpserver`` by replacing the former line with the latter. This is +actually recommended if you rely on proxying from Apache or Nginx to a +``pserve`` -invoked application. **The wsgiref server is not a production +quality server.** See :ref:`alternate_wsgi_server` for more information. + +.. warning:: + + Previously, paste.httpserver "helped" by converting header values that weren't + strings to strings. The wsgiref server, on the other hand implements the spec + more fully. This specifically may affect you if you are modifying headers on + your response. The following error might be an indicator of this problem: + **AssertionError: Header values must be strings, please check the type of + the header being returned.** A common case would be returning unicode headers + instead of string headers. + +A new :mod:`pyramid.compat` module was added which provides Python 2/3 +straddling support for Pyramid add-ons and development environments. + +Python 3 compatibility required dropping some package dependencies and +support for older Python versions and platforms. See the "Backwards +Incompatibilities" section below for more information. + +Introspection +~~~~~~~~~~~~~ + +A configuration introspection system was added; see +:ref:`using_introspection` and :ref:`introspection` for more information on +using the introspection system as a developer. + +The latest release of the pyramid debug toolbar (0.9.7+) provides an +"Introspection" panel that exposes introspection information to a Pyramid +application developer. + +New APIs were added to support introspection +:attr:`pyramid.registry.Introspectable`, +:attr:`pyramid.registry.noop_introspector`, +:attr:`pyramid.config.Configurator.introspector`, +:attr:`pyramid.config.Configurator.introspectable`, +:attr:`pyramid.registry.Registry.introspector`. + +``@view_defaults`` Decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you use a class as a view, you can use the new +:class:`pyramid.view.view_defaults` class decorator on the class to provide +defaults to the view configuration information used by every ``@view_config`` +decorator that decorates a method of that class. + +For instance, if you've got a class that has methods that represent "REST +actions", all which are mapped to the same route, but different request +methods, instead of this: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + class RESTView(object): + def __init__(self, request): + self.request = request + + @view_config(route_name='rest', request_method='GET') + def get(self): + return Response('get') + + @view_config(route_name='rest', request_method='POST') + def post(self): + return Response('post') + + @view_config(route_name='rest', request_method='DELETE') + def delete(self): + return Response('delete') + +You can do this: + +.. code-block:: python + :linenos: + + from pyramid.view import view_defaults + from pyramid.view import view_config + from pyramid.response import Response + + @view_defaults(route_name='rest') + class RESTView(object): + def __init__(self, request): + self.request = request + + @view_config(request_method='GET') + def get(self): + return Response('get') + + @view_config(request_method='POST') + def post(self): + return Response('post') + + @view_config(request_method='DELETE') + def delete(self): + return Response('delete') + +This also works for imperative view configurations that involve a class. + +See :ref:`view_defaults` for more information. + +Minor Feature Additions +----------------------- + +- New APIs: :class:`pyramid.path.AssetResolver` and + :class:`pyramid.path.DottedNameResolver`. The former can be used to + resolve an :term:`asset specification` to an API that can be used to read + the asset's data, the latter can be used to resolve a :term:`dotted Python + name` to a module or a package. + +- A ``mako.directories`` setting is no longer required to use Mako templates + Rationale: Mako template renderers can be specified using an absolute asset + spec. An entire application can be written with such asset specs, + requiring no ordered lookup path. + +- ``bpython`` interpreter compatibility in ``pshell``. See + :ref:`ipython_or_bpython` for more information. + +- Added :func:`pyramid.paster.get_appsettings` API function. This function + returns the settings defined within an ``[app:...]`` section in a + PasteDeploy ``ini`` file. + +- Added :func:`pyramid.paster.setup_logging` API function. This function + sets up Python logging according to the logging configuration in a + PasteDeploy ``ini`` file. + +- Configuration conflict reporting is reported in a more understandable way + ("Line 11 in file..." vs. a repr of a tuple of similar info). + +- We allow extra keyword arguments to be passed to the + :meth:`pyramid.config.Configurator.action` method. + +Backwards Incompatibilities +--------------------------- + +- Pyramid no longer runs on Python 2.5 (which includes the most recent + release of Jython and the Python 2.5 version of GAE as of this writing). + +- The ``paster`` command is no longer the documented way to create projects, + start the server, or run debugging commands. To create projects from + scaffolds, ``paster create`` is replaced by the ``pcreate`` console script. + To serve up a project, ``paster serve`` is replaced by the ``pserve`` + console script. New console scripts named ``pshell``, ``pviews``, + ``proutes``, and ``ptweens`` do what their ``paster <commandname>`` + equivalents used to do. All relevant narrative documentation has been + updated. Rationale: the Paste and PasteScript packages do not run under + Python 3. + +- The default WSGI server run as the result of ``pserve`` from newly rendered + scaffolding is now the ``wsgiref`` WSGI server instead of the + ``paste.httpserver`` server. ``wsgiref``, unlike the server it replaced + (``paste.httpserver``) is not a production quality server. See + :ref:`alternate_wsgi_server` for information about how to use another WSGI + server in production. Rationale: the Paste and PasteScript packages do not + run under Python 3. + +- The ``pshell`` command (see "paster pshell") no longer accepts a + ``--disable-ipython`` command-line argument. Instead, it accepts a ``-p`` + or ``--python-shell`` argument, which can be any of the values ``python``, + ``ipython`` or ``bpython``. + +- Removed the ``pyramid.renderers.renderer_from_name`` function. It has been + deprecated since Pyramid 1.0, and was never an API. + +- To use ZCML with versions of Pyramid >= 1.3, you will need ``pyramid_zcml`` + version >= 0.8 and ``zope.configuration`` version >= 3.8.0. The + ``pyramid_zcml`` package version 0.8 is backwards compatible all the way to + Pyramid 1.0, so you won't be warned if you have older versions installed + and upgrade Pyramid itself "in-place"; it may simply break instead + (particularly if you use ZCML's ``includeOverrides`` directive). + +Documentation Enhancements +-------------------------- + +- The :ref:`bfg_sql_wiki_tutorial` has been updated. It now uses + ``@view_config`` decorators and an explicit database population script. + +- Minor updates to the :ref:`bfg_wiki_tutorial`. + +- A narrative documentation chapter named :ref:`extconfig_narr` was added; it + describes how to add a custom :term:`configuration directive`, and how use + the :meth:`pyramid.config.Configurator.action` method within custom + directives. It also describes how to add :term:`introspectable` objects. + +- A narrative documentation chapter named :ref:`using_introspection` was + added. It describes how to query the introspection system. + +Dependency Changes +------------------ + +- Pyramid no longer depends on the ``zope.component`` package, except as a + testing dependency. + +- Pyramid now depends on the following package versions: + zope.interface>=3.8.0, WebOb>=1.2dev, repoze.lru>=0.4, + zope.deprecation>=3.5.0, translationstring>=0.4 for Python 3 compatibility + purposes. It also, as a testing dependency, depends on WebTest>=1.3.1 for + the same reason. + +- Pyramid no longer depends on the ``Paste`` or ``PasteScript`` packages. + These packages are not Python 3 compatible. + +Scaffolding Changes +------------------- + +- Rendered scaffolds have now been changed to be more relocatable (fewer + mentions of the package name within files in the package). + +- The ``routesalchemy`` scaffold has been renamed ``alchemy``, replacing the + older (traversal-based) ``alchemy`` scaffold (which has been retired). + +- The ``alchemy`` and ``starter`` scaffolds are Python 3 compatible. + +- The ``starter`` scaffold now uses URL dispatch by default. |
