diff options
144 files changed, 2460 insertions, 2658 deletions
diff --git a/.travis.yml b/.travis.yml index 9d4324ff8..29e499e76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,9 @@ python: - 2.7 - pypy - 3.2 + - 3.3 + +install: python setup.py dev script: python setup.py test -q diff --git a/CHANGES.txt b/CHANGES.txt index 9f780fe45..434eab898 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,51 @@ Unreleased ========== +- Avoid crash in ``pserve --reload`` under Py3k, when iterating over posiibly + mutated ``sys.modules``. + +- Fixed a bug in ``UnencryptedCookieSessionFactoryConfig`` and + ``SignedCookieSessionFactory`` where ``timeout=None`` would cause a new + session to always be created. Also in ``SignedCookieSessionFactory`` a + ``reissue_time=None`` would cause an exception when modifying the session. + See https://github.com/Pylons/pyramid/issues/1247 + +1.5b1 (2014-02-08) +================== + +Features +-------- + +- We no longer eagerly clear ``request.exception`` and ``request.exc_info`` in + the exception view tween. This makes it possible to inspect exception + information within a finished callback. See + https://github.com/Pylons/pyramid/issues/1223. + +1.5a4 (2014-01-28) +================== + +Features +-------- + +- Updated scaffolds with new theme, fixed documentation and sample project. + +Bug Fixes +--------- + +- Depend on a newer version of WebOb so that we pull in some crucial bug-fixes + that were showstoppers for functionality in Pyramid. + +- Add a trailing semicolon to the JSONP response. This fixes JavaScript syntax + errors for old IE versions. See https://github.com/Pylons/pyramid/pull/1205 + +- Fix a memory leak when the configurator's ``set_request_property`` method was + used or when the configurator's ``add_request_method`` method was used with + the ``property=True`` attribute. See + https://github.com/Pylons/pyramid/issues/1212 . + +1.5a3 (2013-12-10) +================== + Features -------- @@ -35,11 +80,13 @@ Features See https://github.com/Pylons/pyramid/pull/1149 - Added a new ``SignedCookieSessionFactory`` which is very similar to the - ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on - signing content. The custom serializer arguments to this function should - only focus on serializing, unlike its predecessor which required the - serializer to also perform signing. - See https://github.com/Pylons/pyramid/pull/1142 + ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on signing + content. The custom serializer arguments to this function should only focus + on serializing, unlike its predecessor which required the serializer to also + perform signing. See https://github.com/Pylons/pyramid/pull/1142 . Note + that cookies generated using ``SignedCookieSessionFactory`` are not + compatible with cookies generated using ``UnencryptedCookieSessionFactory``, + so existing user session data will be destroyed if you switch to it. - Added a new ``BaseCookieSessionFactory`` which acts as a generic cookie factory that can be used by framework implementors to create their own @@ -48,17 +95,38 @@ Features timeouts, and conformance with the ``ISession`` API. See https://github.com/Pylons/pyramid/pull/1142 +- The anchor argument to ``pyramid.request.Request.route_url`` and + ``pyramid.request.Request.resource_url`` and their derivatives will now be + escaped via URL quoting to ensure minimal conformance. See + https://github.com/Pylons/pyramid/pull/1183 + +- Allow sending of ``_query`` and ``_anchor`` options to + ``pyramid.request.Request.static_url`` when an external URL is being + generated. + See https://github.com/Pylons/pyramid/pull/1183 + +- You can now send a string as the ``_query`` argument to + ``pyramid.request.Request.route_url`` and + ``pyramid.request.Request.resource_url`` and their derivatives. When a + string is sent instead of a list or dictionary. it is URL-quoted however it + does not need to be in ``k=v`` form. This is useful if you want to be able + to use a different query string format than ``x-www-form-urlencoded``. See + https://github.com/Pylons/pyramid/pull/1183 + +- ``pyramid.testing.DummyRequest`` now has a ``domain`` attribute to match the + new WebOb 1.3 API. Its value is ``example.com``. + Bug Fixes --------- -- Fix the ``pcreate`` script so that when the target directory name ends with a - slash it does not produce a non-working project directory structure. - Previously saying ``pcreate -s starter /foo/bar/`` produced different output +- Fix the ``pcreate`` script so that when the target directory name ends with a + slash it does not produce a non-working project directory structure. + Previously saying ``pcreate -s starter /foo/bar/`` produced different output than saying ``pcreate -s starter /foo/bar``. The former did not work properly. -- Fix the ``principals_allowed_by_permission`` method of - ``ACLAuthorizationPolicy`` so it anticipates a callable ``__acl__`` +- Fix the ``principals_allowed_by_permission`` method of + ``ACLAuthorizationPolicy`` so it anticipates a callable ``__acl__`` on resources. Previously it did not try to call the ``__acl__`` if it was callable. @@ -85,7 +153,7 @@ Documentation - Added a "Quick Tutorial" to go with the Quick Tour -- Removed mention of ``pyramid_beaker`` from docs. Beaker is no longer +- Removed mention of ``pyramid_beaker`` from docs. Beaker is no longer maintained. Point people at ``pyramid_redis_sessions`` instead. - Add documentation for ``pyramid.interfaces.IRendererFactory`` and @@ -108,23 +176,17 @@ Deprecations ill-defined and became unused when Mako and Chameleon template bindings were split into their own packages. -- The ``pyramid.session.UnencryptedCookieSessionFactoryConfig`` API has been - deprecated and is superseded by the +- The ``pyramid.session.UnencryptedCookieSessionFactoryConfig`` API has been + deprecated and is superseded by the ``pyramid.session.SignedCookieSessionFactory``. Note that while the cookies generated by the ``UnencryptedCookieSessionFactoryConfig`` are compatible with cookies generated by old releases, cookies generated by - the SignedCookieSessionFactory are not. See + the SignedCookieSessionFactory are not. See https://github.com/Pylons/pyramid/pull/1142 - The ``pyramid.security.has_permission`` API is now deprecated. Instead, use the newly-added ``has_permission`` method of the request object. -- The ``pyramid.security.forget`` API is now deprecated. Instead, use - the newly-added ``forget_userid`` method of the request object. - -- The ``pyramid.security.remember`` API is now deprecated. Instead, use - the newly-added ``remember_userid`` method of the request object. - - The ``pyramid.security.effective_principals`` API is now deprecated. Instead, use the newly-added ``effective_principals`` attribute of the request object. @@ -137,6 +199,12 @@ Deprecations Instead, use the newly-added ``unauthenticated_userid`` attribute of the request object. +Dependencies +------------ + +- Pyramid now depends on WebOb>=1.3 (it uses ``webob.cookies.CookieProfile`` + from 1.3+). + 1.5a2 (2013-09-22) ================== @@ -144,8 +212,8 @@ Features -------- - Users can now provide dotted Python names to as the ``factory`` argument - the Configurator methods named ``add_{view,route,subscriber}_predicate`` - (instead of passing the predicate factory directly, you can pass a + the Configurator methods named ``add_{view,route,subscriber}_predicate`` + (instead of passing the predicate factory directly, you can pass a dotted name which refers to the factory). Bug Fixes @@ -181,10 +249,10 @@ Backwards Incompatibilities ValueError: No such renderer factory .mak - Support for Mako templating has been moved into an add-on package named - ``pyramid_mako``, and support for Chameleon templating has been moved into - an add-on package named ``pyramid_chameleon``. These packages are drop-in - replacements for the old built-in support for these templating langauges. + Support for Mako templating has been moved into an add-on package named + ``pyramid_mako``, and support for Chameleon templating has been moved into + an add-on package named ``pyramid_chameleon``. These packages are drop-in + replacements for the old built-in support for these templating langauges. All you have to do is install them and make them active in your configuration to register renderer factories for ``.pt`` and/or ``.mako`` (or ``.mak``) to make your application work again. @@ -207,9 +275,9 @@ Backwards Incompatibilities ], ) - * Within the portion of your application which instantiates a Pyramid - ``pyramid.config.Configurator`` (often the ``main()`` function in - your project's ``__init__.py`` file), tell Pyramid to include the + * Within the portion of your application which instantiates a Pyramid + ``pyramid.config.Configurator`` (often the ``main()`` function in + your project's ``__init__.py`` file), tell Pyramid to include the ``pyramid_mako`` includeme:: config = Configurator(.....) @@ -219,7 +287,7 @@ Backwards Incompatibilities * Make sure the ``pyramid_chameleon`` package is installed. One way to do this is by adding ``pyramid_chameleon`` to the ``install_requires`` section - of your package's ``setup.py`` file and afterwards rerunning + of your package's ``setup.py`` file and afterwards rerunning ``setup.py develop``:: setup( @@ -231,9 +299,9 @@ Backwards Incompatibilities ], ) - * Within the portion of your application which instantiates a Pyramid - ``~pyramid.config.Configurator`` (often the ``main()`` function in - your project's ``__init__.py`` file), tell Pyramid to include the + * Within the portion of your application which instantiates a Pyramid + ``~pyramid.config.Configurator`` (often the ``main()`` function in + your project's ``__init__.py`` file), tell Pyramid to include the ``pyramid_chameleon`` includeme:: config = Configurator(.....) @@ -246,10 +314,10 @@ Backwards Incompatibilities compatibility when you eventually do upgrade to Pyramid 1.5. With the removal of Mako and Chameleon support from the core, some - unit tests that use the ``pyramid.renderers.render*`` methods may begin to - fail. If any of your unit tests are invoking either + unit tests that use the ``pyramid.renderers.render*`` methods may begin to + fail. If any of your unit tests are invoking either ``pyramid.renderers.render()`` or ``pyramid.renderers.render_to_response()`` - with either Mako or Chameleon templates then the + with either Mako or Chameleon templates then the ``pyramid.config.Configurator`` instance in effect during the unit test should be also be updated to include the addons, as shown above. For example:: @@ -273,17 +341,17 @@ Backwards Incompatibilities result = pyramid.renderers.render('mypkg:templates/home.pt', {}) - If you're using the Pyramid debug toolbar, when you upgrade Pyramid to - 1.5a2+, you'll also need to upgrade the ``pyramid_debugtoolbar`` package to - at least version 1.0.8, as older toolbar versions are not compatible with - Pyramid 1.5a2+ due to the removal of Mako support from the core. It's + 1.5a2+, you'll also need to upgrade the ``pyramid_debugtoolbar`` package to + at least version 1.0.8, as older toolbar versions are not compatible with + Pyramid 1.5a2+ due to the removal of Mako support from the core. It's fine to use this newer version of the toolbar code with older Pyramids too. - Removed the ``request.response_*`` varying attributes. These attributes have been deprecated since Pyramid 1.1, and as per the deprecation policy, have now been removed. -- ``request.response`` will no longer be mutated when using the - ``pyramid.renderers.render()`` API. Almost all renderers mutate the +- ``request.response`` will no longer be mutated when using the + ``pyramid.renderers.render()`` API. Almost all renderers mutate the ``request.response`` response object (for example, the JSON renderer sets ``request.response.content_type`` to ``application/json``), but this is only necessary when the renderer is generating a response; it was a bug @@ -291,7 +359,7 @@ Backwards Incompatibilities - Removed the ``bfg2pyramid`` fixer script. -- The ``pyramid.events.NewResponse`` event is now sent **after** response +- The ``pyramid.events.NewResponse`` event is now sent **after** response callbacks are executed. It previously executed before response callbacks were executed. Rationale: it's more useful to be able to inspect the response after response callbacks have done their jobs instead of before. @@ -350,24 +418,24 @@ Features - A new http exception subclass named ``pyramid.httpexceptions.HTTPSuccessful`` was added. You can use this class as the ``context`` of an exception - view to catch all 200-series "exceptions" (e.g. "raise HTTPOk"). This + view to catch all 200-series "exceptions" (e.g. "raise HTTPOk"). This also allows you to catch *only* the ``HTTPOk`` exception itself; previously - this was impossible because a number of other exceptions + this was impossible because a number of other exceptions (such as ``HTTPNoContent``) inherited from ``HTTPOk``, but now they do not. - You can now generate "hybrid" urldispatch/traversal URLs more easily - by using the new ``route_name``, ``route_kw`` and ``route_remainder_name`` + by using the new ``route_name``, ``route_kw`` and ``route_remainder_name`` arguments to ``request.resource_url`` and ``request.resource_path``. See - the new section of the "Combining Traversal and URL Dispatch" documentation + the new section of the "Combining Traversal and URL Dispatch" documentation chapter entitled "Hybrid URL Generation". -- It is now possible to escape double braces in Pyramid scaffolds (unescaped, +- It is now possible to escape double braces in Pyramid scaffolds (unescaped, these represent replacement values). You can use ``\{\{a\}\}`` to - represent a "bare" ``{{a}}``. See + represent a "bare" ``{{a}}``. See https://github.com/Pylons/pyramid/pull/862 - Add ``localizer`` and ``locale_name`` properties (reified) to the request. - See https://github.com/Pylons/pyramid/issues/508. Note that the + See https://github.com/Pylons/pyramid/issues/508. Note that the ``pyramid.i18n.get_localizer`` and ``pyramid.i18n.get_locale_name`` functions now simply look up these properties on the request. @@ -417,7 +485,7 @@ Features externally-hosted static URLs to be generated based on the current protocol. - The ``AuthTktAuthenticationPolicy`` has two new options to configure its - domain usage: + domain usage: * ``parent_domain``: if set the authentication cookie is set on the parent domain. This is useful if you have multiple sites sharing the @@ -506,7 +574,7 @@ Features - The ``pyramid.config.Configurator.add_route`` method now supports being called with an external URL as pattern. See - https://github.com/Pylons/pyramid/issues/611 and the documentation section + https://github.com/Pylons/pyramid/issues/611 and the documentation section in the "URL Dispatch" chapter entitled "External Routes" for more information. Bug Fixes @@ -514,17 +582,17 @@ Bug Fixes - It was not possible to use ``pyramid.httpexceptions.HTTPException`` as the ``context`` of an exception view as very general catchall for - http-related exceptions when you wanted that exception view to override the + http-related exceptions when you wanted that exception view to override the default exception view. See https://github.com/Pylons/pyramid/issues/985 -- When the ``pyramid.reload_templates`` setting was true, and a Chameleon - template was reloaded, and the renderer specification named a macro +- When the ``pyramid.reload_templates`` setting was true, and a Chameleon + template was reloaded, and the renderer specification named a macro (e.g. ``foo#macroname.pt``), renderings of the template after the template - was reloaded due to a file change would produce the entire template body - instead of just a rendering of the macro. See + was reloaded due to a file change would produce the entire template body + instead of just a rendering of the macro. See https://github.com/Pylons/pyramid/issues/1013. -- Fix an obscure problem when combining a virtual root with a route with a +- Fix an obscure problem when combining a virtual root with a route with a ``*traverse`` in its pattern. Now the traversal path generated in such a configuration will be correct, instead of an element missing a leading slash. @@ -582,12 +650,12 @@ Backwards Incompatibilities previously returned the URL without the query string by default, it now does attach the query string unless it is overriden. -- The ``route_url`` and ``route_path`` APIs no longer quote ``/`` +- The ``route_url`` and ``route_path`` APIs no longer quote ``/`` to ``%2F`` when a replacement value contains a ``/``. This was pointless, - as WSGI servers always unquote the slash anyway, and Pyramid never sees the + as WSGI servers always unquote the slash anyway, and Pyramid never sees the quoted value. -- It is no longer possible to set a ``locale_name`` attribute of the request, +- It is no longer possible to set a ``locale_name`` attribute of the request, nor is it possible to set a ``localizer`` attribute of the request. These are now "reified" properties that look up a locale name and localizer respectively using the machinery described in the "Internationalization" diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 6dba1076e..d1ac72df5 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -226,3 +226,7 @@ Contributors - Karl O. Pinc, 2013/09/27 - Matthew Russell, 2013/10/14 + +- Antti Haapala, 2013/11/15 + +- Amit Mane, 2014/01/23 diff --git a/HACKING.txt b/HACKING.txt index b32a8a957..460d02047 100644 --- a/HACKING.txt +++ b/HACKING.txt @@ -1,45 +1,64 @@ Hacking on Pyramid ================== -Here are some guidelines about hacking on Pyramid. +Here are some guidelines for hacking on Pyramid. Using a Development Checkout ---------------------------- -You'll have to create a development environment to hack on Pyramid, using a -Pyramid checkout. You can either do this by hand or, if you have ``tox`` -installed (it's on PyPI), you can (ab)use tox to get a working development +You'll have to create a development environment to hack on Pyramid, using a +Pyramid checkout. You can either do this by hand, or if you have ``tox`` +installed (it's on PyPI), you can use tox to set up a working development environment. Each installation method is described below. By Hand +++++++ -- Check out Pyramid from source:: +- While logged into your GitHub account, navigate to the Pyramid repo on + GitHub. + + https://github.com/Pylons/pyramid + +- Fork and clone the Pyramid repository to your GitHub account by clicking + the "Fork" button. + +- Clone your fork of Pyramid from your GitHub account to your local computer, + substituting your account username and specifying the destination as + "hack-on-pyramid". $ cd ~ - $ git clone git://github.com/Pylons/pyramid.git hack-on-pyramid + $ git clone git@github.com:USERNAME/pyramid.git hack-on-pyramid $ cd hack-on-pyramid + # Configure remotes such that you can pull changes from the Pyramid + # repository into your local repository. + $ git remote add upstream git@github.com:Pylons/pyramid.git + # fetch and merge changes from upstream into master + $ git fetch upstream + $ git merge upstream/master + +Now your local repo is set up such that you will push changes to your GitHub +repo, from which you can submit a pull request. -- Create a virtualenv in which to install Pyramid:: +- Create a virtualenv in which to install Pyramid: $ cd ~/hack-on-pyramid $ virtualenv -ppython2.7 env - Note that very old versions of virtualenv (virtualenv versions below, say, + Note that very old versions of virtualenv (virtualenv versions below, say, 1.10 or thereabouts) require you to pass a ``--no-site-packages`` flag to get a completely isolated environment. - You can choose which Python version you want to use by passing a ``-p`` + You can choose which Python version you want to use by passing a ``-p`` flag to ``virtualenv``. For example, ``virtualenv -ppython2.7`` chooses the Python 2.7 interpreter to be installed. - From here on in within these instructions, the ``~/hack-on-pyramid/env`` + From here on in within these instructions, the ``~/hack-on-pyramid/env`` virtual environment you created above will be referred to as ``$VENV``. To use the instructions in the steps that follow literally, use the ``export VENV=~/hack-on-pyramid/env`` command. - Install ``setuptools-git`` into the virtualenv (for good measure, as we're - using git to do version control):: + using git to do version control): $ $VENV/bin/easy_install setuptools-git @@ -47,19 +66,18 @@ By Hand dev``. ``setup.py dev`` is an alias for "setup.py develop" which also installs testing requirements such as nose and coverage. Running ``setup.py dev`` *must* be done while the current working directory is the - ``pyramid`` checkout directory:: + ``pyramid`` checkout directory: $ cd ~/hack-on-pyramid $ $VENV/bin/python setup.py dev -- At that point, you should be able to create new Pyramid projects by using - ``pcreate``:: +- Optionally create a new Pyramid project using ``pcreate``: $ cd $VENV $ bin/pcreate -s starter starter -- And install those projects (also using ``setup.py develop``) into the - virtualenv:: +- ...and install the new project (also using ``setup.py develop``) into the + virtualenv: $ cd $VENV/starter $ $VENV/bin/python setup.py develop @@ -70,12 +88,12 @@ Using Tox Alternatively, if you already have ``tox`` installed, there is an easier way to get going. -- Create a new directory somewhere and ``cd`` to it:: +- Create a new directory somewhere and ``cd`` to it: $ mkdir ~/hack-on-pyramid $ cd ~/hack-on-pyramid -- Check out a read-only copy of the Pyramid source:: +- Check out a read-only copy of the Pyramid source: $ git clone git://github.com/Pylons/pyramid.git . @@ -85,14 +103,14 @@ Since Pyramid is a framework and not an application, it can be convenient to work against a sample application, preferably in its own virtualenv. A quick way to achieve this is to (ab-)use ``tox`` (http://tox.readthedocs.org/en/latest/) with a custom configuration -file that's part of the checkout:: +file that's part of the checkout: tox -c hacking-tox.ini This will create a python-2.7 based virtualenv named ``env27`` (Pyramid's ``.gitconfig` ignores all top-level folders that start with ``env`` specifically for this use case) and inside that a simple pyramid application named -``hacking`` that you can then fire up like so:: +``hacking`` that you can then fire up like so: cd env27/hacking ../bin/pserve development.ini @@ -132,7 +150,7 @@ Coding Style - PEP8 compliance. Whitespace rules are relaxed: not necessary to put 2 newlines between classes. But 80-column lines, in particular, are - mandatory. See + mandatory. See http://docs.pylonsproject.org/en/latest/community/codestyle.html for more information. @@ -142,14 +160,14 @@ Coding Style Running Tests -------------- -- To run all tests for Pyramid on a single Python version, run ``nosetests`` +- To run all tests for Pyramid on a single Python version, run ``nosetests`` from your development virtualenv (See *Using a Development Checkout* above). - To run individual tests (i.e. during development) you can use a regular expression with the ``-t`` parameter courtesy of the `nose-selecttests - <https://pypi.python.org/pypi/nose-selecttests/>`_ plugin that's been - installed (along with nose itself) via ``python setup.py dev``. The - easiest usage is to simply provide the verbatim name of the test you're + <https://pypi.python.org/pypi/nose-selecttests/>`_ plugin that's been + installed (along with nose itself) via ``python setup.py dev``. The + easiest usage is to simply provide the verbatim name of the test you're working on. - To run the full set of Pyramid tests on all platforms, install ``tox`` @@ -159,7 +177,7 @@ Running Tests invoke the ``tox`` console script. This will read the ``tox.ini`` file and execute the tests on multiple Python versions and platforms; while it runs, it creates a virtualenv for each version/platform combination. For - example:: + example: $ sudo /usr/bin/easy_install tox $ cd ~/hack-on-pyramid/ @@ -167,7 +185,7 @@ Running Tests - The tests can also be run using ``pytest`` (http://pytest.org/). This is intended as a convenience for people who are more used or fond of ``pytest``. - Run the tests like so:: + Run the tests like so: $ $VENV/bin/easy_install pytest $ py.test --strict pyramid/ @@ -184,39 +202,47 @@ Documentation Coverage and Building HTML Documentation ------------------------------------------------------ If you fix a bug, and the bug requires an API or behavior modification, all -documentation in this package which references that API or behavior must -change to reflect the bug fix, ideally in the same commit that fixes the bug +documentation in this package which references that API or behavior must be +changed to reflect the bug fix, ideally in the same commit that fixes the bug or adds the feature. To build and review docs (where ``$VENV`` refers to the virtualenv you're using to develop Pyramid): -1. After following the steps above in "Using a Development Checkout", cause - Sphinx and all development requirements to be installed in your - virtualenv:: +1. After following the steps above in "Using a Development Checkout", install + Sphinx and all development requirements in your virtualenv: $ cd ~/hack-on-pyramid $ $VENV/bin/python setup.py docs 2. Update all git submodules from the top-level of your Pyramid checkout, like - so:: + so: $ git submodule update --init --recursive This will checkout theme subrepositories and prevent error conditions when HTML docs are generated. -3. cd to the ``docs`` directory within your Pyramid checkout and execute - the ``make`` command with some flags:: +3. Next change into the submodule's directory and switch to a branch so that + the submodule repositories are no longer "headless". Then update the + repository to ensure that we have the latest updates. + See http://chrisjean.com/2009/04/20/git-submodules-adding-using-removing-and-updating/ + + $ cd docs/_themes + $ git checkout master + $ git pull + +4. Change into the ``docs`` directory within your Pyramid checkout and execute + the ``make`` command with some flags: $ cd ~/hack-on-pyramid/pyramid/docs $ make clean html SPHINXBUILD=$VENV/bin/sphinx-build - The ``SPHINXBUILD=...`` hair is there in order to tell it to use the - virtualenv Python, which will have both Sphinx and Pyramid (for API - documentation generation) installed. + The ``SPHINXBUILD=...`` argument tells Sphinx to use the virtualenv Python, + which will have both Sphinx and Pyramid (for API documentation generation) + installed. -4. Open the ``docs/_build/html/index.html`` file to see the resulting HTML +5. Open the ``docs/_build/html/index.html`` file to see the resulting HTML rendering. Change Log @@ -227,4 +253,3 @@ Change Log descriptive, not cryptic. Other developers should be able to know what your changelog entry means. - diff --git a/docs/_themes b/docs/_themes -Subproject 26732645619b372764097e5e8086f89871d90c0 +Subproject 3bec9280a6cedb15e97e5899021aa8d723c2538 diff --git a/docs/api/i18n.rst b/docs/api/i18n.rst index 53e8c8a9b..3b9abbc1d 100644 --- a/docs/api/i18n.rst +++ b/docs/api/i18n.rst @@ -7,7 +7,7 @@ .. autoclass:: TranslationString - .. autoclass:: TranslationStringFactory + .. autofunction:: TranslationStringFactory .. autoclass:: Localizer :members: diff --git a/docs/api/registry.rst b/docs/api/registry.rst index 7736cf075..bab3e26ba 100644 --- a/docs/api/registry.rst +++ b/docs/api/registry.rst @@ -21,7 +21,10 @@ 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`. + + .. seealso:: + + 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. diff --git a/docs/api/request.rst b/docs/api/request.rst index b7604020e..343d0c022 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -250,8 +250,11 @@ ``invoke_subrequest`` isn't *actually* a method of the Request object; it's a callable added when the Pyramid router is invoked, or when a subrequest is invoked. This means that it's not available for use on a - request provided by e.g. the ``pshell`` environment. For more - information, see :ref:`subrequest_chapter`. + request provided by e.g. the ``pshell`` environment. + + .. seealso:: + + See also :ref:`subrequest_chapter`. .. automethod:: has_permission @@ -280,7 +283,11 @@ This property will return the JSON-decoded variant of the request body. If the request body is not well-formed JSON, or there is no body associated with this request, this property will raise an - exception. See also :ref:`request_json_body`. + exception. + + .. seealso:: + + See also :ref:`request_json_body`. .. method:: set_property(callable, name=None, reify=False) diff --git a/docs/designdefense.rst b/docs/designdefense.rst index bbce3e29c..1ed4f65a4 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -419,7 +419,7 @@ hierarchical: sections within sections within sections, ad infinitum. If you want your URLs to indicate this structure, and the structure is indefinite (the number of nested sections can be "N" instead of some fixed number), a resource tree is an excellent way to model this, even if the backend is a -relational database. In this situation, the resource tree a just a site +relational database. In this situation, the resource tree is just a site structure. Traversal also offers better composability of applications than URL dispatch, @@ -537,7 +537,11 @@ text indexing. It does not dictate how you arrange your code. Such opinionated functionality exists in applications and frameworks built *on top* of :app:`Pyramid`. It's intended that higher-level systems emerge -built using :app:`Pyramid` as a base. See also :ref:`apps_are_extensible`. +built using :app:`Pyramid` as a base. + +.. seealso:: + + See also :ref:`apps_are_extensible`. Pyramid Provides Too Many "Rails" --------------------------------- @@ -1078,7 +1082,7 @@ The contents of ``app2.py``: The contents of ``config.py``: .. code-block:: python - :linenos: + :linenos: L = [] diff --git a/docs/glossary.rst b/docs/glossary.rst index 406b81778..0e340491b 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -54,8 +54,12 @@ Glossary provides an API for addressing "asset files" within a Python :term:`package`. Asset files are static files, template files, etc; basically anything non-Python-source that lives in a Python package can - be considered a asset file. See also `PkgResources - <http://peak.telecommunity.com/DevCenter/PkgResources>`_ + be considered a asset file. + + .. seealso:: + + See also `PkgResources + <http://peak.telecommunity.com/DevCenter/PkgResources>`_. asset Any file contained within a Python :term:`package` which is *not* @@ -242,7 +246,11 @@ Glossary be effectively amended with a ``permission`` argument that will require that the executing user possess the default permission in order to successfully execute the associated :term:`view - callable` See also :ref:`setting_a_default_permission`. + callable`. + + .. seealso:: + + See also :ref:`setting_a_default_permission`. ACE An *access control entry*. An access control entry is one element @@ -380,7 +388,11 @@ Glossary route A single pattern matched by the :term:`url dispatch` subsystem, which generally resolves to a :term:`root factory` (and then - ultimately a :term:`view`). See also :term:`url dispatch`. + ultimately a :term:`view`). + + .. seealso:: + + See also :term:`url dispatch`. route configuration Route configuration is the act of associating request parameters with a @@ -580,8 +592,11 @@ Glossary A wrapper around a Python function or class which accepts the function or class as its first argument and which returns an arbitrary object. :app:`Pyramid` provides several decorators, - used for configuration and return value modification purposes. See - also `PEP 318 <http://www.python.org/dev/peps/pep-0318/>`_. + used for configuration and return value modification purposes. + + .. seealso:: + + See also `PEP 318 <http://www.python.org/dev/peps/pep-0318/>`_. configuration declaration An individual method call made to a :term:`configuration directive`, @@ -646,8 +661,11 @@ Glossary HTTP Exception The set of exception classes defined in :mod:`pyramid.httpexceptions`. These can be used to generate responses with various status codes when - raised or returned from a :term:`view callable`. See also - :ref:`http_exceptions`. + raised or returned from a :term:`view callable`. + + .. seealso:: + + See also :ref:`http_exceptions`. thread local A thread-local variable is one which is essentially a global variable @@ -656,8 +674,11 @@ Glossary application may have a different value for this same "global" variable. :app:`Pyramid` uses a small number of thread local variables, as described in :ref:`threadlocals_chapter`. - See also the :class:`stdlib documentation <threading.local>` - for more information. + + .. seealso:: + + See also the :class:`stdlib documentation <threading.local>` + for more information. multidict An ordered dictionary that can have multiple values for each key. Adds @@ -671,7 +692,11 @@ Glossary Agendaless Consulting A consulting organization formed by Paul Everitt, Tres Seaver, - and Chris McDonough. See also http://agendaless.com . + and Chris McDonough. + + .. seealso:: + + See also `Agendaless Consulting <http://agendaless.com>`_. Jython A `Python implementation <http://www.jython.org/>`_ written for @@ -792,15 +817,21 @@ Glossary The act of creating software with a user interface that can potentially be displayed in more than one language or cultural context. Often shortened to "i18n" (because the word - "internationalization" is I, 18 letters, then N). See also: - :term:`Localization`. + "internationalization" is I, 18 letters, then N). + + .. seealso:: + + See also :term:`Localization`. Localization The process of displaying the user interface of an internationalized application in a particular language or cultural context. Often shortened to "l10" (because the word - "localization" is L, 10 letters, then N). See also: - :term:`Internationalization`. + "localization" is L, 10 letters, then N). + + .. seealso:: + + See also :term:`Internationalization`. renderer globals Values injected as names into a renderer by a @@ -809,7 +840,10 @@ Glossary response callback A user-defined callback executed by the :term:`router` at a point after a :term:`response` object is successfully created. - See :ref:`using_response_callbacks`. + + .. seealso:: + + See also :ref:`using_response_callbacks`. finished callback A user-defined callback executed by the :term:`router` diff --git a/docs/narr/MyProject/development.ini b/docs/narr/MyProject/development.ini index 84e08c2d0..a9a26e56b 100644 --- a/docs/narr/MyProject/development.ini +++ b/docs/narr/MyProject/development.ini @@ -1,3 +1,8 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + [app:main] use = egg:MyProject @@ -6,15 +11,26 @@ pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en -pyramid.includes = +pyramid.includes = pyramid_debugtoolbar +# By default, the toolbar only appears for clients from IP addresses +# '127.0.0.1' and '::1'. +# debugtoolbar.hosts = 127.0.0.1 ::1 + +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 -# Begin logging configuration +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### [loggers] keys = root, myproject @@ -42,5 +58,3 @@ formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/narr/MyProject/myproject/static/favicon.ico b/docs/narr/MyProject/myproject/static/favicon.ico Binary files differdeleted file mode 100644 index 71f837c9e..000000000 --- a/docs/narr/MyProject/myproject/static/favicon.ico +++ /dev/null diff --git a/docs/narr/MyProject/myproject/static/footerbg.png b/docs/narr/MyProject/myproject/static/footerbg.png Binary files differdeleted file mode 100644 index 1fbc873da..000000000 --- a/docs/narr/MyProject/myproject/static/footerbg.png +++ /dev/null diff --git a/docs/narr/MyProject/myproject/static/headerbg.png b/docs/narr/MyProject/myproject/static/headerbg.png Binary files differdeleted file mode 100644 index 0596f2020..000000000 --- a/docs/narr/MyProject/myproject/static/headerbg.png +++ /dev/null diff --git a/docs/narr/MyProject/myproject/static/ie6.css b/docs/narr/MyProject/myproject/static/ie6.css deleted file mode 100644 index b7c8493d8..000000000 --- a/docs/narr/MyProject/myproject/static/ie6.css +++ /dev/null @@ -1,8 +0,0 @@ -* html img, -* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", -this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", -this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) -);} -#wrap{display:table;height:100%} diff --git a/docs/narr/MyProject/myproject/static/middlebg.png b/docs/narr/MyProject/myproject/static/middlebg.png Binary files differdeleted file mode 100644 index 2369cfb7d..000000000 --- a/docs/narr/MyProject/myproject/static/middlebg.png +++ /dev/null diff --git a/docs/narr/MyProject/myproject/static/pylons.css b/docs/narr/MyProject/myproject/static/pylons.css deleted file mode 100644 index 4b1c017cd..000000000 --- a/docs/narr/MyProject/myproject/static/pylons.css +++ /dev/null @@ -1,372 +0,0 @@ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td -{ - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; /* 16px */ - vertical-align: baseline; - background: transparent; -} - -body -{ - line-height: 1; -} - -ol, ul -{ - list-style: none; -} - -blockquote, q -{ - quotes: none; -} - -blockquote:before, blockquote:after, q:before, q:after -{ - content: ''; - content: none; -} - -:focus -{ - outline: 0; -} - -ins -{ - text-decoration: none; -} - -del -{ - text-decoration: line-through; -} - -table -{ - border-collapse: collapse; - border-spacing: 0; -} - -sub -{ - vertical-align: sub; - font-size: smaller; - line-height: normal; -} - -sup -{ - vertical-align: super; - font-size: smaller; - line-height: normal; -} - -ul, menu, dir -{ - display: block; - list-style-type: disc; - margin: 1em 0; - padding-left: 40px; -} - -ol -{ - display: block; - list-style-type: decimal-leading-zero; - margin: 1em 0; - padding-left: 40px; -} - -li -{ - display: list-item; -} - -ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl -{ - margin-top: 0; - margin-bottom: 0; -} - -ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir -{ - list-style-type: circle; -} - -ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir -{ - list-style-type: square; -} - -.hidden -{ - display: none; -} - -p -{ - line-height: 1.5em; -} - -h1 -{ - font-size: 1.75em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h2 -{ - font-size: 1.5em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h3 -{ - font-size: 1.25em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h4 -{ - font-size: 1em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -html, body -{ - width: 100%; - height: 100%; -} - -body -{ - margin: 0; - padding: 0; - background-color: #fff; - position: relative; - font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; -} - -a -{ - color: #1b61d6; - text-decoration: none; -} - -a:hover -{ - color: #e88f00; - text-decoration: underline; -} - -body h1, body h2, body h3, body h4, body h5, body h6 -{ - font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; - font-weight: 400; - color: #373839; - font-style: normal; -} - -#wrap -{ - min-height: 100%; -} - -#header, #footer -{ - width: 100%; - color: #fff; - height: 40px; - position: absolute; - text-align: center; - line-height: 40px; - overflow: hidden; - font-size: 12px; - vertical-align: middle; -} - -#header -{ - background: #000; - top: 0; - font-size: 14px; -} - -#footer -{ - bottom: 0; - background: #000 url(footerbg.png) repeat-x 0 top; - position: relative; - margin-top: -40px; - clear: both; -} - -.header, .footer -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.wrapper -{ - width: 100%; -} - -#top, #top-small, #bottom -{ - width: 100%; -} - -#top -{ - color: #000; - height: 230px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#top-small -{ - color: #000; - height: 60px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#bottom -{ - color: #222; - background-color: #fff; -} - -.top, .top-small, .middle, .bottom -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.top -{ - padding-top: 40px; -} - -.top-small -{ - padding-top: 10px; -} - -#middle -{ - width: 100%; - height: 100px; - background: url(middlebg.png) repeat-x; - border-top: 2px solid #fff; - border-bottom: 2px solid #b2b2b2; -} - -.app-welcome -{ - margin-top: 25px; -} - -.app-name -{ - color: #000; - font-weight: 700; -} - -.bottom -{ - padding-top: 50px; -} - -#left -{ - width: 350px; - float: left; - padding-right: 25px; -} - -#right -{ - width: 350px; - float: right; - padding-left: 25px; -} - -.align-left -{ - text-align: left; -} - -.align-right -{ - text-align: right; -} - -.align-center -{ - text-align: center; -} - -ul.links -{ - margin: 0; - padding: 0; -} - -ul.links li -{ - list-style-type: none; - font-size: 14px; -} - -form -{ - border-style: none; -} - -fieldset -{ - border-style: none; -} - -input -{ - color: #222; - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 12px; - line-height: 16px; -} - -input[type=text], input[type=password] -{ - width: 205px; -} - -input[type=submit] -{ - background-color: #ddd; - font-weight: 700; -} - -/*Opera Fix*/ -body:before -{ - content: ""; - height: 100%; - float: left; - width: 0; - margin-top: -32767px; -} diff --git a/docs/narr/MyProject/myproject/static/pyramid-16x16.png b/docs/narr/MyProject/myproject/static/pyramid-16x16.png Binary files differnew file mode 100644 index 000000000..979203112 --- /dev/null +++ b/docs/narr/MyProject/myproject/static/pyramid-16x16.png diff --git a/docs/narr/MyProject/myproject/static/pyramid-small.png b/docs/narr/MyProject/myproject/static/pyramid-small.png Binary files differdeleted file mode 100644 index a5bc0ade7..000000000 --- a/docs/narr/MyProject/myproject/static/pyramid-small.png +++ /dev/null diff --git a/docs/narr/MyProject/myproject/static/pyramid.png b/docs/narr/MyProject/myproject/static/pyramid.png Binary files differindex 347e05549..4ab837be9 100644 --- a/docs/narr/MyProject/myproject/static/pyramid.png +++ b/docs/narr/MyProject/myproject/static/pyramid.png diff --git a/docs/narr/MyProject/myproject/static/theme.css b/docs/narr/MyProject/myproject/static/theme.css new file mode 100644 index 000000000..be50ad420 --- /dev/null +++ b/docs/narr/MyProject/myproject/static/theme.css @@ -0,0 +1,152 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +body { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; + color: #ffffff; + background: #bc2131; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; +} +p { + font-weight: 300; +} +.font-normal { + font-weight: 400; +} +.font-semi-bold { + font-weight: 600; +} +.font-bold { + font-weight: 700; +} +.starter-template { + margin-top: 250px; +} +.starter-template .content { + margin-left: 10px; +} +.starter-template .content h1 { + margin-top: 10px; + font-size: 60px; +} +.starter-template .content h1 .smaller { + font-size: 40px; + color: #f2b7bd; +} +.starter-template .content .lead { + font-size: 25px; + color: #f2b7bd; +} +.starter-template .content .lead .font-normal { + color: #ffffff; +} +.starter-template .links { + float: right; + right: 0; + margin-top: 125px; +} +.starter-template .links ul { + display: block; + padding: 0; + margin: 0; +} +.starter-template .links ul li { + list-style: none; + display: inline; + margin: 0 10px; +} +.starter-template .links ul li:first-child { + margin-left: 0; +} +.starter-template .links ul li:last-child { + margin-right: 0; +} +.starter-template .links ul li.current-version { + color: #f2b7bd; + font-weight: 400; +} +.starter-template .links ul li a { + color: #ffffff; +} +.starter-template .links ul li a:hover { + text-decoration: underline; +} +.starter-template .links ul li .icon-muted { + color: #eb8b95; + margin-right: 5px; +} +.starter-template .links ul li:hover .icon-muted { + color: #ffffff; +} +.starter-template .copyright { + margin-top: 10px; + font-size: 0.9em; + color: #f2b7bd; + text-transform: lowercase; + float: right; + right: 0; +} +@media (max-width: 1199px) { + .starter-template .content h1 { + font-size: 45px; + } + .starter-template .content h1 .smaller { + font-size: 30px; + } + .starter-template .content .lead { + font-size: 20px; + } +} +@media (max-width: 991px) { + .starter-template { + margin-top: 0; + } + .starter-template .logo { + margin: 40px auto; + } + .starter-template .content { + margin-left: 0; + text-align: center; + } + .starter-template .content h1 { + margin-bottom: 20px; + } + .starter-template .links { + float: none; + text-align: center; + margin-top: 60px; + } + .starter-template .copyright { + float: none; + text-align: center; + } +} +@media (max-width: 767px) { + .starter-template .content h1 .smaller { + font-size: 25px; + display: block; + } + .starter-template .content .lead { + font-size: 16px; + } + .starter-template .links { + margin-top: 40px; + } + .starter-template .links ul li { + display: block; + margin: 0; + } + .starter-template .links ul li .icon-muted { + display: none; + } + .starter-template .copyright { + margin-top: 20px; + } +} diff --git a/docs/narr/MyProject/myproject/static/theme.min.css b/docs/narr/MyProject/myproject/static/theme.min.css new file mode 100644 index 000000000..2f924bcc5 --- /dev/null +++ b/docs/narr/MyProject/myproject/static/theme.min.css @@ -0,0 +1 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
\ No newline at end of file diff --git a/docs/narr/MyProject/myproject/static/transparent.gif b/docs/narr/MyProject/myproject/static/transparent.gif Binary files differdeleted file mode 100644 index 0341802e5..000000000 --- a/docs/narr/MyProject/myproject/static/transparent.gif +++ /dev/null diff --git a/docs/narr/MyProject/myproject/templates/mytemplate.pt b/docs/narr/MyProject/myproject/templates/mytemplate.pt index 0fccba624..d1af4f42c 100644 --- a/docs/narr/MyProject/myproject/templates/mytemplate.pt +++ b/docs/narr/MyProject/myproject/templates/mytemplate.pt @@ -1,76 +1,66 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> -<head> - <title>The Pyramid Web Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="/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="/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="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> - </div> - </div> - <div id="middle"> - <div class="middle align-center"> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid Web Framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/current/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Go" /> - </form> +<!DOCTYPE html> +<html lang="${request.locale_name}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('myproject:static/pyramid-16x16.png')}"> + + <title>Starter Template for The Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> + + <!-- Custom styles for this template --> + <link href="${request.static_url('myproject:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('myproject:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">starter template</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p> + </div> + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li class="current-version">Currently v1.5</li> + <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org">Docs</a></li> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> + </div> </div> - <div id="right" class="align-left"> - <h2>Pyramid links</h2> - <ul class="links"> - <li> - <a href="http://pylonsproject.org">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <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/en/1.3-branch/#tutorials">Tutorials</a> - </li> - <li> - <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/en/1.3-branch/#sample-applications">Sample Applications</a> - </li> - <li> - <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> - </li> - </ul> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> </div> </div> </div> - </div> - <div id="footer"> - <div class="footer">© Copyright 2008-2011, Agendaless Consulting.</div> - </div> -</body> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script> + <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script> + </body> </html> diff --git a/docs/narr/MyProject/production.ini b/docs/narr/MyProject/production.ini index 3396125f2..9eae9e03f 100644 --- a/docs/narr/MyProject/production.ini +++ b/docs/narr/MyProject/production.ini @@ -1,3 +1,8 @@ +### +# app configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html +### + [app:main] use = egg:MyProject @@ -7,12 +12,19 @@ pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en +### +# wsgi server configuration +### + [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 -# Begin logging configuration +### +# logging configuration +# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html +### [loggers] keys = root, myproject @@ -40,5 +52,3 @@ formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s - -# End logging configuration diff --git a/docs/narr/MyProject/setup.py b/docs/narr/MyProject/setup.py index a23f46c91..8c019af51 100644 --- a/docs/narr/MyProject/setup.py +++ b/docs/narr/MyProject/setup.py @@ -40,4 +40,3 @@ setup(name='MyProject', main = myproject:main """, ) - diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index d3431e39e..9ceaaa495 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -158,8 +158,12 @@ use :meth:`pyramid.config.Configurator.include`: Using :meth:`~pyramid.config.Configurator.include` instead of calling the function directly provides a modicum of automated conflict resolution, with the configuration statements you define in the calling code overriding those -of the included function. See also :ref:`automatic_conflict_resolution` and -:ref:`including_configuration`. +of the included function. + +.. seealso:: + + See also :ref:`automatic_conflict_resolution` and + :ref:`including_configuration`. Using ``config.commit()`` +++++++++++++++++++++++++ diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 0984b4daf..3cabbd8f4 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -387,12 +387,12 @@ explicit tweens defined in its ``development.ini`` file: Implicit Tween Chain (not used) - Position Name Alias - -------- ---- ----- - - - INGRESS - 0 pyramid_debugtoolbar.toolbar.toolbar_tween_factory pdbt - 1 pyramid.tweens.excview_tween_factory excview - - - MAIN + Position Name + -------- ---- + - INGRESS + 0 pyramid_debugtoolbar.toolbar.toolbar_tween_factory + 1 pyramid.tweens.excview_tween_factory + - MAIN Here's the application configuration section of the ``development.ini`` used by the above ``ptweens`` command which reports that the explicit tween chain diff --git a/docs/narr/environment.rst b/docs/narr/environment.rst index f0c0c18fe..412635f08 100644 --- a/docs/narr/environment.rst +++ b/docs/narr/environment.rst @@ -59,8 +59,11 @@ third-party template rendering extensions. Reloading Assets ---------------- -Don't cache any asset file data when this value is true. See -also :ref:`overriding_assets_section`. +Don't cache any asset file data when this value is true. + +.. seealso:: + + See also :ref:`overriding_assets_section`. +---------------------------------+-----------------------------+ | Environment Variable Name | Config File Setting Name | @@ -79,7 +82,11 @@ Debugging Authorization ----------------------- Print view authorization failure and success information to stderr -when this value is true. See also :ref:`debug_authorization_section`. +when this value is true. + +.. seealso:: + + See also :ref:`debug_authorization_section`. +---------------------------------+-----------------------------------+ | Environment Variable Name | Config File Setting Name | @@ -94,7 +101,11 @@ Debugging Not Found Errors -------------------------- Print view-related ``NotFound`` debug messages to stderr -when this value is true. See also :ref:`debug_notfound_section`. +when this value is true. + +.. seealso:: + + See also :ref:`debug_notfound_section`. +---------------------------------+------------------------------+ | Environment Variable Name | Config File Setting Name | @@ -109,7 +120,11 @@ Debugging Route Matching ------------------------ Print debugging messages related to :term:`url dispatch` route matching when -this value is true. See also :ref:`debug_routematch_section`. +this value is true. + +.. seealso:: + + See also :ref:`debug_routematch_section`. +---------------------------------+--------------------------------+ | Environment Variable Name | Config File Setting Name | @@ -128,7 +143,11 @@ Preventing HTTP Caching Prevent the ``http_cache`` view configuration argument from having any effect globally in this process when this value is true. No http caching-related response headers will be set by the Pyramid ``http_cache`` view configuration -feature when this is true. See also :ref:`influencing_http_caching`. +feature when this is true. + +.. seealso:: + + See also :ref:`influencing_http_caching`. +---------------------------------+----------------------------------+ | Environment Variable Name | Config File Setting Name | @@ -173,8 +192,11 @@ Default Locale Name -------------------- The value supplied here is used as the default locale name when a -:term:`locale negotiator` is not registered. See also -:ref:`localization_deployment_settings`. +:term:`locale negotiator` is not registered. + +.. seealso:: + + See also :ref:`localization_deployment_settings`. +---------------------------------+-----------------------------------+ | Environment Variable Name | Config File Setting Name | diff --git a/docs/narr/events.rst b/docs/narr/events.rst index 2accb3dbe..09caac898 100644 --- a/docs/narr/events.rst +++ b/docs/narr/events.rst @@ -44,7 +44,7 @@ Configuring an Event Listener Imperatively You can imperatively configure a subscriber function to be called for some event type via the :meth:`~pyramid.config.Configurator.add_subscriber` -method (see also :term:`Configurator`): +method: .. code-block:: python :linenos: @@ -63,6 +63,10 @@ The first argument to subscriber function (or a :term:`dotted Python name` which refers to a subscriber callable); the second argument is the event type. +.. seealso:: + + See also :term:`Configurator`. + Configuring an Event Listener Using a Decorator ----------------------------------------------- @@ -172,7 +176,7 @@ track of the information that subscribers will need. Here are some example custom event classes: .. code-block:: python - :linenos: + :linenos: class DocCreated(object): def __init__(self, doc, request): @@ -196,7 +200,7 @@ also use custom events with :ref:`subscriber predicates event with a decorator: .. code-block:: python - :linenos: + :linenos: from pyramid.events import subscriber from .events import DocCreated @@ -215,7 +219,7 @@ To fire your custom events use the accessed as ``request.registry.notify``. For example: .. code-block:: python - :linenos: + :linenos: from .events import DocCreated diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index a60a49fea..8462a9da7 100644 --- a/docs/narr/extending.rst +++ b/docs/narr/extending.rst @@ -234,7 +234,7 @@ For example, if the original application has the following ``configure_views`` configuration method: .. code-block:: python - :linenos: + :linenos: def configure_views(config): config.add_view('theoriginalapp.views.theview', name='theview') diff --git a/docs/narr/hellotraversal.rst b/docs/narr/hellotraversal.rst index 142c24f54..0a93b8f16 100644 --- a/docs/narr/hellotraversal.rst +++ b/docs/narr/hellotraversal.rst @@ -60,10 +60,10 @@ A more complicated application could have many types of resources, with different view callables defined for each type, and even multiple views for each type. -See Also ---------- +.. seealso:: -Full technical details may be found in :doc:`traversal`. - -For more about *why* you might use traversal, see :doc:`muchadoabouttraversal`. + Full technical details may be found in :doc:`traversal`. + + For more about *why* you might use traversal, see + :doc:`muchadoabouttraversal`. diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 0c450fad7..f2542f1d7 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -363,7 +363,7 @@ and modify the set of :term:`renderer globals` before they are passed to a that can be used for this purpose. For example: .. code-block:: python - :linenos: + :linenos: from pyramid.events import subscriber from pyramid.events import BeforeRender @@ -963,8 +963,8 @@ For full details, please read the `Venusian documentation .. _registering_tweens: -Registering "Tweens" --------------------- +Registering Tweens +------------------ .. versionadded:: 1.2 Tweens @@ -976,26 +976,80 @@ feature that may be used by Pyramid framework extensions, to provide, for example, Pyramid-specific view timing support bookkeeping code that examines exceptions before they are returned to the upstream WSGI application. Tweens behave a bit like :term:`WSGI` :term:`middleware` but they have the benefit of -running in a context in which they have access to the Pyramid -:term:`application registry` as well as the Pyramid rendering machinery. +running in a context in which they have access to the Pyramid :term:`request`, +:term:`response` and :term:`application registry` as well as the Pyramid +rendering machinery. -Creating a Tween Factory -~~~~~~~~~~~~~~~~~~~~~~~~ +Creating a Tween +~~~~~~~~~~~~~~~~ -To make use of tweens, you must construct a "tween factory". A tween factory +To create a tween, you must write a "tween factory". A tween factory must be a globally importable callable which accepts two arguments: ``handler`` and ``registry``. ``handler`` will be the either the main Pyramid request handling function or another tween. ``registry`` will be the Pyramid :term:`application registry` represented by this Configurator. A -tween factory must return a tween when it is called. +tween factory must return the tween (a callable object) when it is called. -A tween is a callable which accepts a :term:`request` object and returns -a :term:`response` object. +A tween is called with a single argument, ``request``, which is the +:term:`request` created by Pyramid's router when it receives a WSGI request. +A tween should return a :term:`response`, usually the one generated by the +downstream Pyramid application. -Here's an example of a tween factory: +You can write the tween factory as a simple closure-returning function: .. code-block:: python - :linenos: + :linenos: + + def simple_tween_factory(handler, registry): + # one-time configuration code goes here + + def simple_tween(request): + # code to be executed for each request before + # the actual application code goes here + + response = handler(request) + + # code to be executed for each request after + # the actual application code goes here + + return response + + return simple_tween + +Alternatively, the tween factory can be a class with the ``__call__`` magic +method: + +.. code-block:: python + :linenos: + + class simple_tween_factory(object): + def __init__(handler, registry): + self.handler = handler + self.registry = registry + + # one-time configuration code goes here + + def __call__(self, request): + # code to be executed for each request before + # the actual application code goes here + + response = self.handler(request) + + # code to be executed for each request after + # the actual application code goes here + + return response + +The closure style performs slightly better and enables you to conditionally +omit the tween from the request processing pipeline (see the following timing +tween example), whereas the class style makes it easier to have shared mutable +state, and it allows subclassing. + +Here's a complete example of a tween that logs the time spent processing each +request: + +.. code-block:: python + :linenos: # in a module named myapp.tweens @@ -1022,12 +1076,6 @@ Here's an example of a tween factory: # handler return handler -If you remember, a tween is an object which accepts a :term:`request` object -and which returns a :term:`response` argument. The ``request`` argument to a -tween will be the request created by Pyramid's router when it receives a WSGI -request. The response object will be generated by the downstream Pyramid -application and it should be returned by the tween. - In the above example, the tween factory defines a ``timing_tween`` tween and returns it if ``asbool(registry.settings.get('do_timing'))`` is true. It otherwise simply returns the handler it was given. The ``registry.settings`` @@ -1053,7 +1101,7 @@ Here's an example of registering a tween factory as an "implicit" tween in a Pyramid application: .. code-block:: python - :linenos: + :linenos: from pyramid.config import Configurator config = Configurator() @@ -1087,7 +1135,7 @@ chain (the tween generated by the very last tween factory added) as its request handler function. For example: .. code-block:: python - :linenos: + :linenos: from pyramid.config import Configurator @@ -1132,8 +1180,10 @@ Allowable values for ``under`` or ``over`` (or both) are: fallbacks if the desired tween is not included, as well as compatibility with multiple other tweens. -Effectively, ``under`` means "closer to the main Pyramid application than", -``over`` means "closer to the request ingress than". +Effectively, ``over`` means "closer to the request ingress than" and +``under`` means "closer to the main Pyramid application than". +You can think of an onion with outer layers over the inner layers, +the application being under all the layers at the center. For example, the following call to :meth:`~pyramid.config.Configurator.add_tween` will attempt to place the @@ -1329,7 +1379,7 @@ route predicate factory is most often a class with a constructor method. For example: .. code-block:: python - :linenos: + :linenos: class ContentTypePredicate(object): def __init__(self, val, config): @@ -1392,7 +1442,7 @@ with a subscriber that subscribes to the :class:`pyramid.events.NewRequest` event type. .. code-block:: python - :linenos: + :linenos: class RequestPathStartsWith(object): def __init__(self, val, config): @@ -1421,7 +1471,7 @@ previously registered ``request_path_startswith`` predicate in a call to :meth:`~pyramid.config.Configurator.add_subscriber`: .. code-block:: python - :linenos: + :linenos: # define a subscriber in your code @@ -1437,7 +1487,7 @@ Here's the same subscriber/predicate/event-type combination used via :class:`~pyramid.events.subscriber`. .. code-block:: python - :linenos: + :linenos: from pyramid.events import subscriber diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst index a29ccb2ac..4a3258d35 100644 --- a/docs/narr/hybrid.rst +++ b/docs/narr/hybrid.rst @@ -63,7 +63,7 @@ An application that uses only traversal will have view configuration declarations that look like this: .. code-block:: python - :linenos: + :linenos: # config is an instance of pyramid.config.Configurator diff --git a/docs/narr/i18n.rst b/docs/narr/i18n.rst index b62c16ff0..5f50ca212 100644 --- a/docs/narr/i18n.rst +++ b/docs/narr/i18n.rst @@ -309,7 +309,7 @@ In particular, add the ``Babel`` and ``lingua`` distributions to the application's ``setup.py`` file: .. code-block:: python - :linenos: + :linenos: setup(name="mypackage", # ... @@ -370,7 +370,7 @@ file of a ``pcreate`` -generated :app:`Pyramid` application has stanzas in it that look something like the following: .. code-block:: ini - :linenos: + :linenos: [compile_catalog] directory = myproject/locale @@ -398,7 +398,7 @@ that you'd like the domain of your translations to be ``mydomain`` instead, change the ``setup.cfg`` file stanzas to look like so: .. code-block:: ini - :linenos: + :linenos: [compile_catalog] directory = myproject/locale @@ -607,10 +607,8 @@ object, but the domain and mapping information attached is ignored. def aview(request): localizer = request.localizer num = 1 - translated = localizer.pluralize( - _('item_plural', default="${number} items"), - None, num, 'mydomain', mapping={'number':num} - ) + translated = localizer.pluralize('item_plural', '${number} items', + num, 'mydomain', mapping={'number':num}) The corresponding message catalog must have language plural definitions and plural alternatives set. @@ -1041,7 +1039,7 @@ if no locale can be determined. Here's an implementation of a simple locale negotiator: .. code-block:: python - :linenos: + :linenos: def my_locale_negotiator(request): locale_name = request.params.get('my_locale') diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index a9c5fdfbd..a37d74c9b 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -121,7 +121,9 @@ ways. .. literalinclude:: helloworld.py -See also :ref:`firstapp_chapter`. +.. seealso:: + + See also :ref:`firstapp_chapter`. Decorator-based configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -271,7 +273,9 @@ Here's a few views defined as methods of a class instead: def view_two(self): return Response('two') -See also :ref:`view_config_placement`. +.. seealso:: + + See also :ref:`view_config_placement`. .. _intro_asset_specs: @@ -336,7 +340,7 @@ For example, instead of returning a ``Response`` object from a ``render_to_response`` call: .. code-block:: python - :linenos: + :linenos: from pyramid.renderers import render_to_response @@ -347,7 +351,7 @@ For example, instead of returning a ``Response`` object from a You can return a Python dictionary: .. code-block:: python - :linenos: + :linenos: from pyramid.view import view_config @@ -572,7 +576,10 @@ For example: config.include('pyramid_exclog') config.include('some.other.guys.package', route_prefix='/someotherguy') -See also :ref:`including_configuration` and :ref:`building_an_extensible_app` +.. seealso:: + + See also :ref:`including_configuration` and + :ref:`building_an_extensible_app`. Flexible authentication and authorization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -730,7 +737,9 @@ Pyramid defaults to explicit behavior, because it's the most generally useful, but provides hooks that allow you to adapt the framework to localized aesthetic desires. -See also :ref:`using_iresponse`. +.. seealso:: + + See also :ref:`using_iresponse`. "Global" response object ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -748,7 +757,9 @@ section," you say. Fine. Be that way: response.content_type = 'text/plain' return response -See also :ref:`request_response_attr`. +.. seealso:: + + See also :ref:`request_response_attr`. Automating repetitive configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -811,7 +822,9 @@ it up and calling :meth:`~pyramid.config.Configurator.add_directive` from within a function called when another user uses the :meth:`~pyramid.config.Configurator.include` method against your code. -See also :ref:`add_directive`. +.. seealso:: + + See also :ref:`add_directive`. Programmatic Introspection ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -827,7 +840,7 @@ Here's an example of using Pyramid's introspector from within a view callable: .. code-block:: python - :linenos: + :linenos: from pyramid.view import view_config from pyramid.response import Response @@ -839,7 +852,9 @@ callable: route_intr = introspector.get('routes', route_name) return Response(str(route_intr['pattern'])) -See also :ref:`using_introspection`. +.. seealso:: + + See also :ref:`using_introspection`. Python 3 Compatibility ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst index 3c0a6744f..a7bde4cf7 100644 --- a/docs/narr/introspector.rst +++ b/docs/narr/introspector.rst @@ -24,7 +24,7 @@ Here's an example of using Pyramid's introspector from within a view callable: .. code-block:: python - :linenos: + :linenos: from pyramid.view import view_config from pyramid.response import Response @@ -100,7 +100,7 @@ its ``__getitem__``, ``get``, ``keys``, ``values``, or ``items`` methods. For example: .. code-block:: python - :linenos: + :linenos: route_intr = introspector.get('routes', 'edit_user') pattern = route_intr['pattern'] diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index b3bfb8a1e..75428d513 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -179,7 +179,7 @@ file, simply create a logger object using the ``__name__`` builtin and call methods on it. .. code-block:: python - :linenos: + :linenos: import logging log = logging.getLogger(__name__) diff --git a/docs/narr/project-debug.png b/docs/narr/project-debug.png Binary files differindex d13a91736..4f8e441ef 100644 --- a/docs/narr/project-debug.png +++ b/docs/narr/project-debug.png diff --git a/docs/narr/project.png b/docs/narr/project.png Binary files differindex fc00ec086..5d46df0dd 100644 --- a/docs/narr/project.png +++ b/docs/narr/project.png diff --git a/docs/narr/project.rst b/docs/narr/project.rst index d7292d187..62b91de0e 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -74,6 +74,9 @@ In :ref:`installing_chapter` we called the virtualenv directory ``env``; the following commands assume that our current working directory is the ``env`` directory. +The below example uses the ``pcreate`` command to create a project with the +``starter`` scaffold. + On UNIX: .. code-block:: text @@ -85,21 +88,7 @@ Or on Windows: .. code-block:: text > %VENV%\Scripts\pcreate -s starter MyProject - -The above command uses the ``pcreate`` command to create a project with the -``starter`` scaffold. To use a different scaffold, such as -``alchemy``, you'd just change the ``-s`` argument value. For example, -on UNIX: - -.. code-block:: text - - $ $VENV/bin/pcreate -s alchemy MyProject - -Or on Windows: - -.. code-block:: text - - > %VENV%\Scripts\pcreate -s alchemy MyProject + Here's sample output from a run of ``pcreate`` on UNIX for a project we name ``MyProject``: @@ -487,23 +476,24 @@ structure: .. code-block:: text MyProject/ - |-- CHANGES.txt - |-- development.ini - |-- MANIFEST.in - |-- myproject - | |-- __init__.py - | |-- static - | | |-- favicon.ico - | | |-- logo.png - | | `-- pylons.css - | |-- templates - | | `-- mytemplate.pt - | |-- tests.py - | `-- views.py - |-- production.ini - |-- README.txt - |-- setup.cfg - `-- setup.py + ├── CHANGES.txt + ├── MANIFEST.in + ├── README.txt + ├── development.ini + ├── myproject + │  ├── __init__.py + │  ├── static + │  │  ├── pyramid-16x16.png + │  │  ├── pyramid.png + │  │  ├── theme.css + │  │  └── theme.min.css + │  ├── templates + │  │  └── mytemplate.pt + │  ├── tests.py + │  └── views.py + ├── production.ini + ├── setup.cfg + └── setup.py The ``MyProject`` :term:`Project` --------------------------------- @@ -908,15 +898,22 @@ returns the HTML in a :term:`response`. a server restart to reload them. Production applications should use ``pyramid.reload_templates = False``. -.. seealso:: See also :ref:`views_which_use_a_renderer` for more information +.. seealso:: + + See also :ref:`views_which_use_a_renderer` for more information about how views, renderers, and templates relate and cooperate. -.. seealso:: Pyramid can also dynamically reload changed Python files. For - more on this see :ref:`reloading_code`. +.. seealso:: + + Pyramid can also dynamically reload changed Python files. See also + :ref:`reloading_code`. + +.. seealso:: -.. seealso:: The :ref:`debug_toolbar` provides interactive access to your - application's internals and, should an exception occur, allows interactive - access to traceback execution stack frames from the Python interpreter. + See also the :ref:`debug_toolbar`, which provides interactive access to + your application's internals and, should an exception occur, allows + interactive access to traceback execution stack frames from the Python + interpreter. .. index:: single: static directory diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst index 34d75f2cc..6139154ff 100644 --- a/docs/narr/resources.rst +++ b/docs/narr/resources.rst @@ -83,7 +83,7 @@ works against resource instances. Here's a sample resource tree, represented by a variable named ``root``: .. code-block:: python - :linenos: + :linenos: class Resource(dict): pass @@ -673,8 +673,11 @@ Calling ``find_interface(b, Thing2)`` will return the ``b`` resource. The second argument to find_interface may also be a :term:`interface` instead of a class. If it is an interface, each resource in the lineage is checked to see if the resource implements the specificed interface (instead of seeing -if the resource is of a class). See also -:ref:`resources_which_implement_interfaces`. +if the resource is of a class). + +.. seealso:: + + See also :ref:`resources_which_implement_interfaces`. .. index:: single: resource API functions diff --git a/docs/narr/scaffolding.rst b/docs/narr/scaffolding.rst index 534b2caf4..f924d0d62 100644 --- a/docs/narr/scaffolding.rst +++ b/docs/narr/scaffolding.rst @@ -39,9 +39,9 @@ named ``__init__.py`` with something like the following: from pyramid.scaffolds import PyramidTemplate - class CoolExtensionTemplate(PyramidTemplate): - _template_dir = 'coolextension_scaffold' - summary = 'My cool extension' + class CoolExtensionTemplate(PyramidTemplate): + _template_dir = 'coolextension_scaffold' + summary = 'My cool extension' Once this is done, within the ``scaffolds`` directory, create a template directory. Our example used a template directory named @@ -89,7 +89,7 @@ For example: [pyramid.scaffold] coolextension=coolextension.scaffolds:CoolExtensionTemplate """ - ) + ) Run your distribution's ``setup.py develop`` or ``setup.py install`` command. After that, you should be able to see your scaffolding template @@ -112,7 +112,7 @@ want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X, defining your scaffold template: .. code-block:: python - :linenos: + :linenos: try: # pyramid 1.0.X # "pyramid.paster.paste_script..." doesn't exist past 1.0.X diff --git a/docs/narr/security.rst b/docs/narr/security.rst index 9e6fb6c82..8db23a33b 100644 --- a/docs/narr/security.rst +++ b/docs/narr/security.rst @@ -113,9 +113,11 @@ authorization policies, it is an error to configure a Pyramid application with an authentication policy but without the authorization policy or vice versa. If you do this, you'll receive an error at application startup time. -See also the :mod:`pyramid.authorization` and -:mod:`pyramid.authentication` modules for alternate implementations -of authorization and authentication policies. +.. seealso:: + + See also the :mod:`pyramid.authorization` and + :mod:`pyramid.authentication` modules for alternate implementations of + authorization and authentication policies. .. index:: single: permissions @@ -495,8 +497,14 @@ is said to be *location-aware*. Location-aware objects define an ``__parent__`` attribute which points at their parent object. The root object's ``__parent__`` is ``None``. -See :ref:`location_module` for documentations of functions which use -location-awareness. See also :ref:`location_aware`. +.. seealso:: + + See also :ref:`location_module` for documentations of functions which use + location-awareness. + +.. seealso:: + + See also :ref:`location_aware`. .. index:: single: forbidden view diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index fb5035373..8da743a01 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -158,10 +158,24 @@ Some gotchas: Using Alternate Session Factories --------------------------------- -At the time of this writing, exactly one project-endorsed alternate session -factory exists named :term:`pyramid_redis_sessions`. It can be downloaded from -PyPI. It uses the Redis database as a backend. It is the recommended -persistent session solution at the time of this writing. +The following session factories exist at the time of this writing. + +======================= ======= ============================= +Session Factory Backend Description +======================= ======= ============================= +pyramid_redis_sessions_ Redis_ Server-side session library + for Pyramid, using Redis for + storage. +pyramid_beaker_ Beaker_ Session factory for Pyramid + backed by the Beaker + sessioning system. +======================= ======= ============================= + +.. _pyramid_redis_sessions: https://pypi.python.org/pypi/pyramid_redis_sessions +.. _Redis: http://redis.io/ + +.. _pyramid_beaker: https://pypi.python.org/pypi/pyramid_beaker +.. _Beaker: http://beaker.readthedocs.org/en/latest/ .. index:: single: session factory (custom) diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index 1affa1758..7b4a7ea08 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -123,7 +123,7 @@ Here's a high-level time-ordered overview of what happens when you press populated by other methods run against the Configurator. The router is a WSGI application. -#. A :class:`~pyramid.events.ApplicationCreated` event is emitted (see +#. An :class:`~pyramid.events.ApplicationCreated` event is emitted (see :ref:`events_chapter` for more information about events). #. Assuming there were no errors, the ``main`` function in ``myproject`` diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 3e19f7198..038dd2594 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -38,8 +38,8 @@ within the body of a view callable like so: from pyramid.renderers import render_to_response def sample_view(request): - return render_to_response('templates/foo.pt', - {'foo':1, 'bar':2}, + return render_to_response('templates/foo.pt', + {'foo':1, 'bar':2}, request=request) The ``sample_view`` :term:`view callable` function above returns a @@ -56,7 +56,7 @@ In this case, this is the directory containing the file that defines the ``sample_view`` function. Although a renderer path is usually just a simple relative pathname, a path named as a renderer can be absolute, starting with a slash on UNIX or a drive letter -prefix on Windows. The path can alternately be a +prefix on Windows. The path can alternately be an :term:`asset specification` in the form ``some.dotted.package_name:relative/path``. This makes it possible to address template assets which live in another package. For example: @@ -73,11 +73,11 @@ address template assets which live in another package. For example: An asset specification points at a file within a Python *package*. In this case, it points at a file named ``foo.pt`` within the -``templates`` directory of the ``mypackage`` package. Using a +``templates`` directory of the ``mypackage`` package. Using an asset specification instead of a relative template name is usually a good idea, because calls to :func:`~pyramid.renderers.render_to_response` using asset specifications will continue to work properly if you move the -code containing them around. +code containing them to another location. In the examples above we pass in a keyword argument named ``request`` representing the current :app:`Pyramid` request. Passing a request @@ -94,8 +94,8 @@ Every view must return a :term:`response` object, except for views which use a :term:`renderer` named via view configuration (which we'll see shortly). The :func:`pyramid.renderers.render_to_response` function is a shortcut function that actually returns a response -object. This allows the example view above to simply return the result -of its call to ``render_to_response()`` directly. +object. This allows the example view above to simply return the result +of its call to ``render_to_response()`` directly. Obviously not all APIs you might call to get response data will return a response object. For example, you might render one or more templates to @@ -111,8 +111,8 @@ as the body of the response: from pyramid.response import Response def sample_view(request): - result = render('mypackage:templates/foo.pt', - {'foo':1, 'bar':2}, + result = render('mypackage:templates/foo.pt', + {'foo':1, 'bar':2}, request=request) response = Response(result) return response @@ -194,7 +194,7 @@ of :func:`~pyramid.renderers.render` (a string): def sample_view(request): result = render('mypackage:templates/foo.pt', - {'foo':1, 'bar':2}, + {'foo':1, 'bar':2}, request=request) response = Response(result) response.content_type = 'text/plain' @@ -241,7 +241,7 @@ These values are provided to the template: The renderer name used to perform the rendering, e.g. ``mypackage:templates/foo.pt``. -``renderer_info`` +``renderer_info`` An object implementing the :class:`pyramid.interfaces.IRendererInfo` interface. Basically, an object with the following attributes: ``name``, ``package`` and ``type``. @@ -273,7 +273,7 @@ Templates Used as Renderers via Configuration An alternative to using :func:`~pyramid.renderers.render_to_response` to render templates manually in your view callable code, is to specify the template as a :term:`renderer` in your -*view configuration*. This can be done with any of the +*view configuration*. This can be done with any of the templating languages supported by :app:`Pyramid`. To use a renderer via view configuration, specify a template @@ -320,7 +320,11 @@ template renderer: in Chameleon, not in Mako templates. Similar renderer configuration can be done imperatively. See -:ref:`views_which_use_a_renderer`. See also :ref:`built_in_renderers`. +:ref:`views_which_use_a_renderer`. + +.. seealso:: + + See also :ref:`built_in_renderers`. Although a renderer path is usually just a simple relative pathname, a path named as a renderer can be absolute, starting with a slash on UNIX or a drive diff --git a/docs/narr/testing.rst b/docs/narr/testing.rst index 5a5bf8fad..e001ad81c 100644 --- a/docs/narr/testing.rst +++ b/docs/narr/testing.rst @@ -319,8 +319,10 @@ registering resources at paths, registering event listeners, registering views and view permissions, and classes representing "dummy" implementations of a request and a resource. -See also the various methods of the :term:`Configurator` documented in -:ref:`configuration_module` that begin with the ``testing_`` prefix. +.. seealso:: + + See also the various methods of the :term:`Configurator` documented in + :ref:`configuration_module` that begin with the ``testing_`` prefix. .. index:: single: integration tests diff --git a/docs/narr/upgrading.rst b/docs/narr/upgrading.rst index 64343ca3e..eb3194a65 100644 --- a/docs/narr/upgrading.rst +++ b/docs/narr/upgrading.rst @@ -137,7 +137,7 @@ In the above case, it's line #3 in the ``myproj.views`` module (``from pyramid.view import static``) that is causing the problem: .. code-block:: python - :linenos: + :linenos: from pyramid.view import view_config @@ -148,7 +148,7 @@ The deprecation warning tells me how to fix it, so I can change the code to do things the newer way: .. code-block:: python - :linenos: + :linenos: from pyramid.view import view_config diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 61849c3c0..87a962a9a 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -492,7 +492,7 @@ The simplest route declaration which configures a route match to *directly* result in a particular view callable being invoked: .. code-block:: python - :linenos: + :linenos: config.add_route('idea', 'site/{id}') config.add_view('mypackage.views.site_view', route_name='idea') @@ -901,7 +901,7 @@ Details of the route matching decision for a particular request to the which you started the application from. For example: .. code-block:: text - :linenos: + :linenos: $ PYRAMID_DEBUG_ROUTEMATCH=true $VENV/bin/pserve development.ini Starting server in PID 13586. @@ -1060,7 +1060,7 @@ A custom route predicate may also *modify* the ``match`` dictionary. For instance, a predicate might do some type conversion of values: .. code-block:: python - :linenos: + :linenos: def integers(*segment_names): def predicate(info, request): @@ -1086,7 +1086,7 @@ To avoid the try/except uncertainty, the route pattern can contain regular expressions specifying requirements for that marker. For instance: .. code-block:: python - :linenos: + :linenos: def integers(*segment_names): def predicate(info, request): @@ -1128,7 +1128,7 @@ name. The ``pattern`` attribute is the route pattern. An example of using the route in a set of route predicates: .. code-block:: python - :linenos: + :linenos: def twenty_ten(info, request): if info['route'].name in ('ymd', 'ym', 'y'): @@ -1183,9 +1183,10 @@ still easily do it by wrapping it in classmethod call. Same will work with staticmethod, just use ``staticmethod`` instead of ``classmethod``. +.. seealso:: -See also :class:`pyramid.interfaces.IRoute` for more API documentation about -route objects. + See also :class:`pyramid.interfaces.IRoute` for more API documentation + about route objects. .. index:: single: route factory diff --git a/docs/narr/vhosting.rst b/docs/narr/vhosting.rst index d37518052..53f6888b3 100644 --- a/docs/narr/vhosting.rst +++ b/docs/narr/vhosting.rst @@ -109,7 +109,7 @@ An example of an Apache ``mod_proxy`` configuration that will host the is below: .. code-block:: apache - :linenos: + :linenos: NameVirtualHost *:80 @@ -130,7 +130,7 @@ For a :app:`Pyramid` application running under :term:`mod_wsgi`, the same can be achieved using ``SetEnv``: .. code-block:: apache - :linenos: + :linenos: <Location /> SetEnv HTTP_X_VHM_ROOT /cms diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index e5a2c1ade..adc53bd11 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -118,8 +118,9 @@ Non-Predicate Arguments ``renderer`` Denotes the :term:`renderer` implementation which will be used to construct - a :term:`response` from the associated view callable's return value. (see - also :ref:`renderers_chapter`). + a :term:`response` from the associated view callable's return value. + + .. seealso:: See also :ref:`renderers_chapter`. This is either a single string term (e.g. ``json``) or a string implying a path or :term:`asset specification` (e.g. ``templates/views.pt``) naming a @@ -217,7 +218,21 @@ Non-Predicate Arguments decorator function will be called with the view callable as a single argument. The view callable it is passed will accept ``(context, request)``. The decorator must return a replacement view callable which - also accepts ``(context, request)``. + also accepts ``(context, request)``. The ``decorator`` may also be an + iterable of decorators, in which case they will be applied one after the + other to the view, in reverse order. For example:: + + @view_config(..., decorator=(decorator2, decorator1)) + def myview(request): + ... + + Is similar to doing:: + + @view_config(...) + @decorator2 + @decorator1 + def myview(request): + ... ``mapper`` A Python object or :term:`dotted Python name` which refers to a :term:`view diff --git a/docs/narr/views.rst b/docs/narr/views.rst index b2dd549ce..a746eb043 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -536,7 +536,7 @@ The following types work as view callables in this style: e.g.: .. code-block:: python - :linenos: + :linenos: from pyramid.response import Response diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 2db18c8a7..4ab39bb11 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -49,7 +49,7 @@ production support in October 2011.) some optional C extensions for performance. With ``easy_install``, Windows users can get these extensions without needing a C compiler. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial section on Requirements <qtut_requirements>`, :ref:`installing_unix`, :ref:`Before You Install <installing_chapter>`, and @@ -73,14 +73,14 @@ This simple example is easy to run. Save this as ``app.py`` and run it: Next, open `http://localhost:6543/ <http://localhost:6543/>`_ in a browser and you will see the ``Hello World!`` message. -New to Python web programming? If so, some lines in module merit +New to Python web programming? If so, some lines in the module merit explanation: #. *Line 10*. The ``if __name__ == '__main__':`` is Python's way of saying "Start here when running from the command line". #. *Lines 11-13*. Use Pyramid's :term:`configurator` to connect - :term:`view` code to particular URL :term:`route`. + :term:`view` code to a particular URL :term:`route`. #. *Lines 6-7*. Implement the view code that generates the :term:`response`. @@ -92,7 +92,7 @@ in Pyramid development. Building an application from loosely-coupled parts via :doc:`../narr/configuration` is a central idea in Pyramid, one that we will revisit regurlarly in this *Quick Tour*. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Hello World <qtut_hello_world>`, :ref:`firstapp_chapter`, and :ref:`Single File Tasks tutorial <tutorials:single-file-tutorial>` @@ -125,7 +125,7 @@ the name is included in the body of the response:: Finally, we set the response's content type and return the Response. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Request and Response <qtut_request_response>` and :ref:`webob_chapter` @@ -148,15 +148,15 @@ So far our examples place everything in one file: - the WSGI application launcher Let's move the views out to their own ``views.py`` module and change -the ``app.py`` to scan that module, looking for decorators that setup +the ``app.py`` to scan that module, looking for decorators that set up the views. First, our revised ``app.py``: .. literalinclude:: quick_tour/views/app.py :linenos: We added some more routes, but we also removed the view code. -Our views, and their registrations (via decorators) are now in a module -``views.py`` which is scanned via ``config.scan('views')``. +Our views and their registrations (via decorators) are now in a module +``views.py``, which is scanned via ``config.scan('views')``. We now have a ``views.py`` module that is focused on handling requests and responses: @@ -167,7 +167,7 @@ and responses: We have 4 views, each leading to the other. If you start at ``http://localhost:6543/``, you get a response with a link to the next view. The ``hello_view`` (available at the URL ``/howdy``) has a link -to the ``redirect_view``, which shows issuing a redirect to the final +to the ``redirect_view``, which issues a redirect to the final view. Earlier we saw ``config.add_view`` as one way to configure a view. This @@ -178,7 +178,7 @@ configuration`, in which a Python :term:`decorator` is placed on the line above the view. Both approaches result in the same final configuration, thus usually, it is simply a matter of taste. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Views <qtut_views>`, :doc:`../narr/views`, :doc:`../narr/viewconfig`, and @@ -226,7 +226,7 @@ view: "replacement patterns" (the curly braces) in the route declaration. This information can then be used in your view. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Routing <qtut_routing>`, :doc:`../narr/urldispatch`, :ref:`debug_routematch_section`, and @@ -243,8 +243,23 @@ Pyramid doesn't mandate a particular database system, form library, etc. It encourages replaceability. This applies equally to templating, which is fortunate: developers have strong views about template languages. That said, the Pylons Project officially supports bindings for -Chameleon, Jinja2 and Mako, so in this step, let's use Chameleon as an -example: +Chameleon, Jinja2, and Mako, so in this step, let's use Chameleon. + +Let's add ``pyramid_chameleon``, a Pyramid :term:`add-on` which enables +Chameleon as a :term:`renderer` in our Pyramid applications: + +.. code-block:: bash + + $ easy_install pyramid_chameleon + +With the package installed, we can include the template bindings into +our configuration: + +.. code-block:: python + + config.include('pyramid_chameleon') + +Now lets change our views.py file: .. literalinclude:: quick_tour/templating/views.py :start-after: Start View 1 @@ -252,7 +267,7 @@ example: Ahh, that looks better. We have a view that is focused on Python code. Our ``@view_config`` decorator specifies a :term:`renderer` that points -our template file. Our view then simply returns data which is then +to our template file. Our view then simply returns data which is then supplied to our template: .. literalinclude:: quick_tour/templating/hello_world.pt @@ -262,7 +277,7 @@ Since our view returned ``dict(name=request.matchdict['name'])``, we can use ``name`` as a variable in our template via ``${name}``. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Templating <qtut_templating>`, :doc:`../narr/templates`, :ref:`debugging_templates`, and @@ -288,7 +303,7 @@ our configuration: config.include('pyramid_jinja2') -The only change in our view...point the renderer at the ``.jinja2`` file: +The only change in our view is to point the renderer at the ``.jinja2`` file: .. literalinclude:: quick_tour/jinja2/views.py :start-after: Start View 1 @@ -305,7 +320,7 @@ filename extensions. In this case, changing the extension from ``.pt`` to ``.jinja2`` passed the view response through the ``pyramid_jinja2`` renderer. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Jinja2 <qtut_jinja2>`, `Jinja2 homepage <http://jinja.pocoo.org/>`_, and :ref:`pyramid_jinja2 Overview <jinja2:overview>` @@ -341,8 +356,8 @@ template: This link presumes that our CSS is at a URL starting with ``/static/``. What if the site is later moved under ``/somesite/static/``? Or perhaps -web developer changes the arrangement on disk? Pyramid gives a helper -that provides flexibility on URL generation: +a web developer changes the arrangement on disk? Pyramid provides a helper +to allow flexibility on URL generation: .. literalinclude:: quick_tour/static_assets/hello_world.pt :language: html @@ -353,7 +368,7 @@ By using ``request.static_url`` to generate the full URL to the static assets, you both ensure you stay in sync with the configuration and gain refactoring flexibility later. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Static Assets <qtut_static_assets>`, :doc:`../narr/assets`, :ref:`preventing_http_caching`, and @@ -374,7 +389,7 @@ This wires up a view that returns some data through the JSON :term:`renderer`, which calls Python's JSON support to serialize the data into JSON and set the appropriate HTTP headers. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial JSON <qtut_json>`, :ref:`views_which_use_a_renderer`, :ref:`json_renderer`, and @@ -431,7 +446,7 @@ have much more to offer: ``accept``, ``header``, ``xhr``, ``containment``, and ``custom_predicates`` -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial View Classes <qtut_view_classes>`, :ref:`Quick Tutorial More View Classes <qtut_more_view_classes>`, and :ref:`class_as_view` @@ -439,7 +454,7 @@ have much more to offer: Quick Project Startup with Scaffolds ==================================== -So far we have done all of our *Quick Glance* as a single Python file. +So far we have done all of our *Quick Tour* as a single Python file. No Python packages, no structure. Most Pyramid projects, though, aren't developed this way. @@ -464,7 +479,7 @@ let's use that scaffold to make our project: $ pcreate --scaffold pyramid_jinja2_starter hello_world -We next use the normal Python development to setup our package for +We next use the normal Python command to set up our package for development: .. code-block:: bash @@ -482,7 +497,7 @@ configuration. This includes a new way of running your application: Let's look at ``pserve`` and configuration in more depth. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Scaffolds <qtut_scaffolds>`, :ref:`project_narr`, and :doc:`../narr/scaffolding` @@ -513,13 +528,13 @@ Most of the work, though, comes from your project's wiring, as expressed in the configuration file you supply to ``pserve``. Let's take a look at this configuration file. -.. seealso:: See Also: +.. seealso:: See also: :ref:`what_is_this_pserve_thing` Configuration with ``.ini`` Files ================================= -Earlier in *Quick Glance* we first met Pyramid's configuration system. +Earlier in *Quick Tour* we first met Pyramid's configuration system. At that point we did all configuration in Python code. For example, the port number chosen for our HTTP server was right there in Python code. Our scaffold has moved this decision, and more, into the @@ -541,8 +556,8 @@ into sections: We have a few decisions made for us in this configuration: -#. *Choice of web server*. The ``use = egg:pyramid#wsgiref`` tell - ``pserve`` to the ``wsgiref`` server that is wrapped in the Pyramid +#. *Choice of web server*. The ``use = egg:pyramid#wsgiref`` tells + ``pserve`` to use the ``wsgiref`` server that is wrapped in the Pyramid package. #. *Port number*. ``port = 6543`` tells ``wsgiref`` to listen on port @@ -559,9 +574,9 @@ We have a few decisions made for us in this configuration: Additionally, the ``development.ini`` generated by this scaffold wired up Python's standard logging. We'll now see in the console, for example, -a log on every request that comes in, as well traceback information. +a log on every request that comes in, as well as traceback information. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Application Configuration <qtut_ini>`, :ref:`environment_chapter` and :doc:`../narr/paste` @@ -570,7 +585,7 @@ a log on every request that comes in, as well traceback information. Easier Development with ``debugtoolbar`` ======================================== -As we introduce the basics we also want to show how to be productive in +As we introduce the basics, we also want to show how to be productive in development and debugging. For example, we just discussed template reloading and earlier we showed ``--reload`` for application reloading. @@ -616,7 +631,7 @@ you want to disable this toolbar, no need to change code: you can remove it from ``pyramid.includes`` in the relevant ``.ini`` configuration file. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial pyramid_debugtoolbar <qtut_debugtoolbar>` and :ref:`pyramid_debugtoolbar <toolbar:overview>` @@ -670,7 +685,7 @@ Pyramid supplies helpers for test writing, which we use in the test setup and teardown. Our one test imports the view, makes a dummy request, and sees if the view returns what we expected. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Unit Testing <qtut_unit_testing>`, :ref:`Quick Tutorial Functional Testing <qtut_functional_testing>`, and @@ -685,12 +700,12 @@ we might need to detect situations when other people use the site. We need *logging*. Fortunately Pyramid uses the normal Python approach to logging. The -scaffold generated, in your ``development.ini``, a number of lines that +scaffold generated in your ``development.ini`` a number of lines that configure the logging for you to some reasonable defaults. You then see -messages sent by Pyramid (for example, when a new request comes in.) +messages sent by Pyramid (for example, when a new request comes in). Maybe you would like to log messages in your code? In your Python -module, import and setup the logging: +module, import and set up the logging: .. literalinclude:: quick_tour/package/hello_world/views.py :start-after: Start Logging 1 @@ -711,13 +726,13 @@ controls that? These sections in the configuration file: :start-after: Start Sphinx Include :end-before: End Sphinx Include -Our application, a package named ``hello_world``, is setup as a logger +Our application, a package named ``hello_world``, is set up as a logger and configured to log messages at a ``DEBUG`` or higher level. When you visit ``http://localhost:6543`` your console will now show:: 2013-08-09 10:42:42,968 DEBUG [hello_world.views][MainThread] Some Message -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Logging <qtut_logging>` and :ref:`logging_chapter` @@ -728,9 +743,9 @@ When people use your web application, they frequently perform a task that requires semi-permanent data to be saved. For example, a shopping cart. This is called a :term:`session`. -Pyramid has basic built-in support for sessions, with add-ons such as -``pyramid_redis_sessions`` (or your own custom sessioning engine) that provide -richer session support. Let's take a look at the +Pyramid has basic built-in support for sessions. Third party packages such as +``pyramid_redis_sessions`` provide richer session support. Or you can create +your own custom sessioning engine. Let's take a look at the :doc:`built-in sessioning support <../narr/sessions>`. In our ``__init__.py`` we first import the kind of sessioning we want: @@ -765,7 +780,7 @@ Jinja2 template: :start-after: Start Sphinx Include 1 :end-before: End Sphinx Include 1 -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Sessions <qtut_sessions>`, :ref:`sessions_chapter`, :ref:`flash_messages`, :ref:`session_module`, and :term:`pyramid_redis_sessions`. @@ -774,7 +789,7 @@ Databases ========= Web applications mean data. Data means databases. Frequently SQL -databases. SQL Databases frequently mean an "ORM" +databases. SQL databases frequently mean an "ORM" (object-relational mapper.) In Python, ORM usually leads to the mega-quality *SQLAlchemy*, a Python package that greatly eases working with databases. @@ -813,7 +828,7 @@ of the system, can then easily get at the data thanks to SQLAlchemy: :start-after: Start Sphinx Include :end-before: End Sphinx Include -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Databases <qtut_databases>`, `SQLAlchemy <http://www.sqlalchemy.org/>`_, :ref:`making_a_console_script`, @@ -876,7 +891,7 @@ Also, the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform widgets using attractive CSS from Bootstrap and more powerful widgets from Chosen. -.. seealso:: See Also: +.. seealso:: See also: :ref:`Quick Tutorial Forms <qtut_forms>`, :ref:`Deform <deform:overview>`, :ref:`Colander <colander:overview>`, and diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst index 8380a75ed..4b4eb1ba3 100644 --- a/docs/quick_tutorial/authentication.rst +++ b/docs/quick_tutorial/authentication.rst @@ -130,5 +130,5 @@ Extra Credit onto each request? Use ``import pdb; pdb.set_trace()`` to answer this. -.. seealso:: :ref:`security_chapter`, - :ref:`AuthTktAuthenticationPolicy <authentication_module>` +.. seealso:: See also :ref:`security_chapter`, + :ref:`AuthTktAuthenticationPolicy <authentication_module>`. diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst index d25588c49..1c540d8a2 100644 --- a/docs/quick_tutorial/debugtoolbar.rst +++ b/docs/quick_tutorial/debugtoolbar.rst @@ -39,7 +39,6 @@ Steps $ $VENV/bin/python setup.py develop $ $VENV/bin/easy_install pyramid_debugtoolbar - #. Our ``debugtoolbar/development.ini`` gets a configuration entry for ``pyramid.includes``: @@ -86,4 +85,29 @@ start to experience otherwise inexplicable client-side weirdness, you can shut it off by commenting out the ``pyramid_debugtoolbar`` line in ``pyramid.includes`` temporarily. -.. seealso:: See Also: :ref:`pyramid_debugtoolbar <toolbar:overview>` +.. seealso:: See also :ref:`pyramid_debugtoolbar <toolbar:overview>`. + +Extra Credit +============ + +# Why don't we add ``pyramid_debugtoolbar`` to the list of + ``install_requires`` dependencies in ``debugtoolbar/setup.py``? + +# Introduce a bug into your application: Change: + + .. code-block:: python + + def hello_world(request): + return Response('<body><h1>Hello World!</h1></body>') + + to: + + .. code-block:: python + + def hello_world(request): + return xResponse('<body><h1>Hello World!</h1></body>') + + Save, and visit http://localhost:6543/ again. Notice the nice + traceback display. On the lowest line, click the "screen" icon to the + right, and try typing the variable names ``request`` and ``Response``. + What else can you discover? diff --git a/docs/quick_tutorial/debugtoolbar/tutorial/__init__.py b/docs/quick_tutorial/debugtoolbar/tutorial/__init__.py index 0993b25be..d784292ee 100644 --- a/docs/quick_tutorial/debugtoolbar/tutorial/__init__.py +++ b/docs/quick_tutorial/debugtoolbar/tutorial/__init__.py @@ -3,7 +3,7 @@ from pyramid.response import Response def hello_world(request): - return xResponse('<body><h1>Hello World!</h1></body>') + return Response('<body><h1>Hello World!</h1></body>') def main(global_config, **settings): diff --git a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt index d1fea0d7f..3292dfd90 100644 --- a/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt +++ b/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt @@ -4,10 +4,10 @@ <title>WikiPage: Add/Edit</title> <tal:block tal:repeat="reqt view.reqts['css']"> <link rel="stylesheet" type="text/css" - href="${request.static_url('deform:static/' + reqt)}"/> + href="${request.static_url(reqt)}"/> </tal:block> <tal:block tal:repeat="reqt view.reqts['js']"> - <script src="${request.static_url('deform:static/' + reqt)}" + <script src="${request.static_url(reqt)}" type="text/javascript"></script> </tal:block> </head> diff --git a/docs/quick_tutorial/hello_world.rst b/docs/quick_tutorial/hello_world.rst index c7a8eaf5e..86e1319f0 100644 --- a/docs/quick_tutorial/hello_world.rst +++ b/docs/quick_tutorial/hello_world.rst @@ -71,11 +71,11 @@ New to Python web programming? If so, some lines in module merit explanation: #. *Line 11*. The ``if __name__ == '__main__':`` is Python's way of - saying "Start here when running from the command line". + saying "Start here when running from the command line", rather than + when this module is imported. #. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect - :term:`view` code to a particular URL - :term:`route`. + :term:`view` code to a particular URL :term:`route`. #. *Lines 6-7*. Implement the view code that generates the :term:`response`. @@ -111,4 +111,4 @@ Extra Credit then reload your browser. See the exception in the console? #. The ``GI`` in ``WSGI`` stands for "Gateway Interface". What web - standard is this modelled after?
\ No newline at end of file + standard is this modelled after? diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst index 618b8e5e8..3402c50e8 100644 --- a/docs/quick_tutorial/ini.rst +++ b/docs/quick_tutorial/ini.rst @@ -98,18 +98,16 @@ the Pyramid chapter on The ``.ini`` file is also used for two other functions: -- *Choice of WSGI server*. ``[server:main]`` wires up the choice of WSGI - *server* for your WSGI *application*. In this case, we are using - ``wsgiref`` bundled in the Python library. +- *Configuring the WSGI server*. ``[server:main]`` wires up the choice of + which WSGI *server* for your WSGI *application*. In this case, we are using + ``wsgiref`` bundled in the Python library. It also wires up the *port + number*: ``port = 6543`` tells ``wsgiref`` to listen on port 6543. -- *Python logging*. Pyramid uses Python standard logging, which needs a - number of configuration values. The ``.ini`` serves this function. +- *Configuring Python logging*. Pyramid uses Python standard logging, which + needs a number of configuration values. The ``.ini`` serves this function. This provides the console log output that you see on startup and each request. -- *Port number*. ``port = 6543`` tells ``wsgiref`` to listen on port - 6543. - We moved our startup code from ``app.py`` to the package's ``tutorial/__init__.py``. This isn't necessary, but it is a common style in Pyramid to take the WSGI app bootstrapping @@ -131,7 +129,7 @@ Extra Credit might you want to do that? #. The entry point in ``setup.py`` didn't mention ``__init__.py`` when - it the ``main`` function. Why not? + it declared ``tutorial:main`` function. Why not? .. seealso:: :ref:`project_narr`, diff --git a/docs/quick_tutorial/logging.rst b/docs/quick_tutorial/logging.rst index 0167e5249..855ded59f 100644 --- a/docs/quick_tutorial/logging.rst +++ b/docs/quick_tutorial/logging.rst @@ -76,4 +76,4 @@ visit http://localhost:6543 your console will now show:: Also, if you have configured your Pyramid application to use the ``pyramid_debugtoolbar``, logging statements appear in one of its menus. -.. seealso:: See Also: :ref:`logging_chapter` +.. seealso:: See also :ref:`logging_chapter`. diff --git a/docs/quick_tutorial/more_view_classes.rst b/docs/quick_tutorial/more_view_classes.rst index 21b353b7c..1e5603554 100644 --- a/docs/quick_tutorial/more_view_classes.rst +++ b/docs/quick_tutorial/more_view_classes.rst @@ -18,11 +18,10 @@ or a Python class. In this last case, methods on the class can be decorated with ``@view_config`` to register the class methods with the :term:`configurator` as a view. -So far our views have been simple, free-standing functions. Many times +At first, our views were simple, free-standing functions. Many times your views are related: different ways to look at or work on the same data or a REST API that handles multiple operations. Grouping these -together as a -:ref:`view class <class_as_view>` makes sense: +together as a :ref:`view class <class_as_view>` makes sense: - Group views @@ -30,9 +29,9 @@ together as a - Share some state and helpers -Pyramid views have -:ref:`view predicates <view_configuration_parameters>` that -help determine which view is matched to a request. These predicates +Pyramid views have :ref:`view predicates <view_configuration_parameters>` +that determine which view is matched to a request, based on factors +such as the request method, the form parameters, etc. These predicates provide many axes of flexibility. The following shows a simple example with four operations operations: diff --git a/docs/quick_tutorial/more_view_classes/tutorial/views.py b/docs/quick_tutorial/more_view_classes/tutorial/views.py index fdba04ba8..635de0520 100644 --- a/docs/quick_tutorial/more_view_classes/tutorial/views.py +++ b/docs/quick_tutorial/more_view_classes/tutorial/views.py @@ -20,7 +20,6 @@ class TutorialViews: def home(self): return {'page_title': 'Home View'} - # Retrieving /howdy/first/last the first time @view_config(renderer='hello.pt') def hello(self): @@ -33,7 +32,8 @@ class TutorialViews: return {'page_title': 'Edit View', 'new_name': new_name} # Posting to /home via the "Delete" submit button - @view_config(request_param='form.delete', renderer='delete.pt') + @view_config(request_method='POST', request_param='form.delete', + renderer='delete.pt') def delete(self): print ('Deleted') return {'page_title': 'Delete View'} diff --git a/docs/quick_tutorial/package.rst b/docs/quick_tutorial/package.rst index 90d022b29..8fb052d5b 100644 --- a/docs/quick_tutorial/package.rst +++ b/docs/quick_tutorial/package.rst @@ -22,8 +22,7 @@ Explaining it all in this tutorial will induce madness. For this tutorial, this is all you need to know: -- We will have a directory for each tutorial step as a - setuptools *project* +- We will have a directory for each tutorial step as a setuptools *project* - This project will contain a ``setup.py`` which injects the features of the setuptool's project machinery into the directory @@ -97,8 +96,8 @@ In this step we have a Python package called ``tutorial``. We use the same name in each step of the tutorial, to avoid unnecessary re-typing. Above this ``tutorial`` directory we have the files that handle the -packaging of this, well, package. At the moment, all we need is a -bare-bones ``ini/setup.py``. +packaging of this project. At the moment, all we need is a +bare-bones ``setup.py``. Everything else is the same about our application. We simply made a Python package with a ``setup.py`` and installed it in development mode. diff --git a/docs/quick_tutorial/requirements.rst b/docs/quick_tutorial/requirements.rst index 234e4aa0d..72bb4a4f8 100644 --- a/docs/quick_tutorial/requirements.rst +++ b/docs/quick_tutorial/requirements.rst @@ -226,25 +226,28 @@ during this tutorial: # Mac and Linux $ $VENV/bin/easy_install nose webtest deform sqlalchemy \ pyramid_chameleon pyramid_debugtoolbar waitress \ - pyramid_jinja2 pyramid_tm zope.sqlalchemy + pyramid_tm zope.sqlalchemy # Windows - c:\> %VENV%\Scripts\easy_install nose webtest deform sqlalchemy pyramid_chameleon - + c:\> %VENV%\Scripts\easy_install nose webtest deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar waitress pyramid_tm zope.sqlalchemy .. note:: Why ``easy_install`` and not ``pip``? Pyramid encourages use of namespace - packages which, until recently, ``pip`` didn't permit. Also, Pyramid has - some optional C extensions for performance. With ``easy_install``, Windows - users can get these extensions without needing a C compiler. - -.. seealso:: See Also: :ref:`installing_unix`. For instructions to set up your + packages, for which ``pip``'s support is less-than-optimal. Also, Pyramid's + dependencies use some optional C extensions for performance: with + ``easy_install``, Windows users can get these extensions without needing + a C compiler (``pip`` does not support installing binary Windows + distributions, except for ``wheels``, which are not yet available for + all dependencies). + +.. seealso:: See also :ref:`installing_unix`. For instructions to set up your Python environment for development using Windows or Python 2, see Pyramid's :ref:`Before You Install <installing_chapter>`. - See also Python 3's :mod:`venv module <python3:venv>`, the `setuptools` `installation instructions + See also Python 3's :mod:`venv module <python3:venv>`, the `setuptools + installation instructions <https://pypi.python.org/pypi/setuptools/0.9.8#installation-instructions>`_, and `easy_install help <https://pypi.python.org/pypi/setuptools/0.9.8#using-setuptools-and-easyinstall>`_. diff --git a/docs/quick_tutorial/scaffolds.rst b/docs/quick_tutorial/scaffolds.rst index 8ca2d27df..4f2694100 100644 --- a/docs/quick_tutorial/scaffolds.rst +++ b/docs/quick_tutorial/scaffolds.rst @@ -63,11 +63,11 @@ Steps On startup, ``pserve`` logs some output: - .. code-block:: bash + .. code-block:: bash - Starting subprocess with file monitor - Starting server in PID 72213. - Starting HTTP server on http://0.0.0.0:6543 + Starting subprocess with file monitor + Starting server in PID 72213. + Starting HTTP server on http://0.0.0.0:6543 #. Open http://localhost:6543/ in your browser. diff --git a/docs/quick_tutorial/templating/setup.py b/docs/quick_tutorial/templating/setup.py index 2221b72e9..0b71b73e6 100644 --- a/docs/quick_tutorial/templating/setup.py +++ b/docs/quick_tutorial/templating/setup.py @@ -2,7 +2,7 @@ from setuptools import setup requires = [ 'pyramid', - 'pyramid_chameleon' + 'pyramid_chameleon', ] setup(name='tutorial', @@ -11,4 +11,4 @@ setup(name='tutorial', [paste.app_factory] main = tutorial:main """, -)
\ No newline at end of file +) diff --git a/docs/quick_tutorial/tutorial_approach.rst b/docs/quick_tutorial/tutorial_approach.rst index 52d768306..204d388b0 100644 --- a/docs/quick_tutorial/tutorial_approach.rst +++ b/docs/quick_tutorial/tutorial_approach.rst @@ -4,9 +4,9 @@ Tutorial Approach This tutorial uses conventions to keep the introduction focused and concise. Details, references, and deeper discussions are mentioned in -"See Also" notes. +"See also" notes. -.. seealso:: This is an example "See Also" note. +.. seealso:: This is an example "See also" note. This "Getting Started" tutorial is broken into independent steps, starting with the smallest possible "single file WSGI app" example. @@ -42,4 +42,4 @@ Each of the first-level directories (e.g. ``request_response``) is a *Python project* (except, as noted, the ``hello_world`` step.) The ``tutorial`` directory is a *Python package*. At the end of each step, we copy a previous directory into a new directory to use as a starting -point.
\ No newline at end of file +point. diff --git a/docs/quick_tutorial/unit_testing.rst b/docs/quick_tutorial/unit_testing.rst index ed33f62d7..f8a33b39d 100644 --- a/docs/quick_tutorial/unit_testing.rst +++ b/docs/quick_tutorial/unit_testing.rst @@ -116,4 +116,4 @@ Extra Credit #. Why do we import the ``hello_world`` view function *inside* the ``test_hello_world`` method instead of at the top of the module? -.. seealso:: See Also: :ref:`testing_chapter` +.. seealso:: See also :ref:`testing_chapter` diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 62b1164e3..93cd0c18e 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -149,8 +149,8 @@ to the ``@view_config`` decorator for ``add_page()`` and ``edit_page()``, for example: .. code-block:: python - :linenos: - :emphasize-lines: 3 + :linenos: + :emphasize-lines: 3 @view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 1e5d0dcbf..2e35574fd 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -207,6 +207,21 @@ routes: :linenos: :language: python +.. note:: The preceding lines must be added *before* the following + ``view_page`` route definition: + + .. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 32 + :linenos: + :language: python + + This is because ``view_page``'s route definition uses a catch-all + "replacement marker" ``/{pagename}`` (see :ref:`route_pattern_syntax`) + which will catch any route that was not already caught by any + route listed above it in ``__init__.py``. Hence, for ``login`` and + ``logout`` views to have the opportunity of being matched + (or "caught"), they must be above ``/{pagename}``. + Add Login and Logout Views ~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py index 42ef77b98..b41d4ab40 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/views.py +++ b/docs/tutorials/wiki2/src/views/tutorial/views.py @@ -1,3 +1,4 @@ +import cgi import re from docutils.core import publish_parts @@ -32,10 +33,10 @@ def view_page(request): exists = DBSession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) - return '<a href="%s">%s</a>' % (view_url, word) + return '<a href="%s">%s</a>' % (view_url, cgi.escape(word)) else: add_url = request.route_url('add_page', pagename=word) - return '<a href="%s">%s</a>' % (add_url, word) + return '<a href="%s">%s</a>' % (add_url, cgi.escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) diff --git a/docs/whatsnew-1.0.rst b/docs/whatsnew-1.0.rst index 8750863e7..9541f0a28 100644 --- a/docs/whatsnew-1.0.rst +++ b/docs/whatsnew-1.0.rst @@ -114,8 +114,11 @@ Scaffold Improvements 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 - the 400 or 500 ranges. See also - http://docs.repoze.org/tm2/#using-a-commit-veto. + the 400 or 500 ranges. + + .. seealso:: + + See also http://docs.repoze.org/tm2/#using-a-commit-veto. Terminology Changes ~~~~~~~~~~~~~~~~~~~ diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst index 086c12ca2..99737b6d8 100644 --- a/docs/whatsnew-1.1.rst +++ b/docs/whatsnew-1.1.rst @@ -454,10 +454,13 @@ Deprecations and Behavior Differences renderer='some/renderer.pt') This deprecation was done to reduce confusion observed in IRC, as well as - to (eventually) reduce documentation burden (see also - https://github.com/Pylons/pyramid/issues/164). A deprecation warning is - now issued when any view-related parameter is passed to - ``add_route``. + to (eventually) reduce documentation burden. A deprecation warning is + now issued when any view-related parameter is passed to ``add_route``. + + .. seealso:: + + See also `issue #164 on GitHub + <https://github.com/Pylons/pyramid/issues/164>`_. - Passing an ``environ`` dictionary to the ``__call__`` method of a "traverser" (e.g. an object that implements @@ -537,8 +540,12 @@ Deprecations and Behavior Differences subdirectory, the ``index.html`` of that subdirectory would not be served properly. Instead, a redirect to ``/subdir`` would be issued. This has been fixed, and now visiting a subdirectory that contains an ``index.html`` - within a static view returns the index.html properly. See also - https://github.com/Pylons/pyramid/issues/67. + within a static view returns the index.html properly. + + .. seealso:: + + See also `issue #67 on GitHub + <https://github.com/Pylons/pyramid/issues/67>`_. - Deprecated the ``pyramid.config.Configurator.set_renderer_globals_factory`` method and the ``renderer_globals`` Configurator constructor parameter. diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst index 57f93cbff..2f73af661 100644 --- a/docs/whatsnew-1.5.rst +++ b/docs/whatsnew-1.5.rst @@ -316,6 +316,56 @@ The feature additions in Pyramid 1.5 follow. - :func:`pyramid.path.package_name` no longer thows an exception when resolving the package name for namespace packages that have no ``__file__`` attribute. +- An authorization API has been added as a method of the request: + :meth:`pyramid.request.Request.has_permission`. It is a method-based + alternative to the :func:`pyramid.security.has_permission` API and works + exactly the same. The older API is now deprecated. + +- Property API attributes have been added to the request for easier access to + authentication data: :attr:`pyramid.request.Request.authenticated_userid`, + :attr:`pyramid.request.Request.unauthenticated_userid`, and + :attr:`pyramid.request.Request.effective_principals`. These are analogues, + respectively, of :func:`pyramid.security.authenticated_userid`, + :func:`pyramid.security.unauthenticated_userid`, and + :func:`pyramid.security.effective_principals`. They operate exactly the + same, except they are attributes of the request instead of functions + accepting a request. They are properties, so they cannot be assigned to. + The older function-based APIs are now deprecated. + +- Pyramid's console scripts (``pserve``, ``pviews``, etc) can now be run + directly, allowing custom arguments to be sent to the python interpreter + at runtime. For example:: + + python -3 -m pyramid.scripts.pserve development.ini + +- Added a specific subclass of :class:`pyramid.httpexceptions.HTTPBadRequest` + named :class:`pyramid.exceptions.BadCSRFToken` which will now be raised in + response to failures in the ``check_csrf_token`` view predicate. See + https://github.com/Pylons/pyramid/pull/1149 + +- Added a new ``SignedCookieSessionFactory`` which is very similar to the + ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on + signing content. The custom serializer arguments to this function should + only focus on serializing, unlike its predecessor which required the + serializer to also perform signing. + See https://github.com/Pylons/pyramid/pull/1142 . Note + that cookies generated using ``SignedCookieSessionFactory`` are not + compatible with cookies generated using ``UnencryptedCookieSessionFactory``, + so existing user session data will be destroyed if you switch to it. + +- Added a new ``BaseCookieSessionFactory`` which acts as a generic cookie + factory that can be used by framework implementors to create their own + session implementations. It provides a reusable API which focuses strictly + on providing a dictionary-like object that properly handles renewals, + timeouts, and conformance with the ``ISession`` API. + See https://github.com/Pylons/pyramid/pull/1142 + +- We no longer eagerly clear ``request.exception`` and ``request.exc_info`` in + the exception view tween. This makes it possible to inspect exception + information within a finished callback. See + https://github.com/Pylons/pyramid/issues/1223. + + Other Backwards Incompatibilities --------------------------------- @@ -404,6 +454,13 @@ Other Backwards Incompatibilities Pyramid narrative documentation instead of providing renderer globals values to the configurator. +- The key/values in the ``_query`` parameter of + :meth:`pyramid.request.Request.route_url` and the ``query`` parameter of + :meth:`pyramid.request.Request.resource_url` (and their variants), used to + encode a value of ``None`` as the string ``'None'``, leaving the resulting + query string to be ``a=b&key=None``. The value is now dropped in this + situation, leaving a query string of ``a=b&key=``. See + https://github.com/Pylons/pyramid/issues/1119 Deprecations ------------ @@ -417,17 +474,48 @@ Deprecations a deprecation warning when used. It had been docs-deprecated in 1.4 but did not issue a deprecation warning when used. +- :func:`pyramid.security.has_permission` is now deprecated in favor of using + :meth:`pyramid.request.Request.has_permission`. + +- The :func:`pyramid.security.authenticated_userid`, + :func:`pyramid.security.unauthenticated_userid`, and + :func:`pyramid.security.effective_principals` functions have been + deprecated. Use :attr:`pyramid.request.Request.authenticated_userid`, + :attr:`pyramid.request.Request.unauthenticated_userid` and + :attr:`pyramid.request.Request.effective_principals` instead. + +- Deprecate the ``pyramid.interfaces.ITemplateRenderer`` interface. It was + ill-defined and became unused when Mako and Chameleon template bindings were + split into their own packages. + +- The ``pyramid.session.UnencryptedCookieSessionFactoryConfig`` API has been + deprecated and is superseded by the + ``pyramid.session.SignedCookieSessionFactory``. Note that while the cookies + generated by the ``UnencryptedCookieSessionFactoryConfig`` + are compatible with cookies generated by old releases, cookies generated by + the SignedCookieSessionFactory are not. See + https://github.com/Pylons/pyramid/pull/1142 + Documentation Enhancements -------------------------- - A new documentation chapter named :ref:`quick_tour` was added. It describes starting out with Pyramid from a high level. +- Added a :ref:`quick_tutorial` to go with the Quick Tour + - Many other enhancements. +Scaffolding Enhancements +------------------------ + +- All scaffolds have a new HTML + CSS theme. + Dependency Changes ------------------ - Pyramid no longer depends upon ``Mako`` or ``Chameleon``. +- Pyramid now depends on WebOb>=1.3 (it uses ``webob.cookies.CookieProfile`` + from 1.3+). diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 2c301bd29..b84981bbc 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -10,6 +10,8 @@ import warnings from zope.interface import implementer +from webob.cookies import CookieProfile + from pyramid.compat import ( long, text_type, @@ -18,6 +20,7 @@ from pyramid.compat import ( url_quote, bytes_, ascii_native_, + native_, ) from pyramid.interfaces import ( @@ -333,12 +336,19 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): return effective_principals def remember(self, request, principal, **kw): - """ Store the ``principal`` as ``repoze.who.userid``.""" + """ Store the ``principal`` as ``repoze.who.userid``. + + The identity to authenticated to :mod:`repoze.who` + will contain the given principal as ``userid``, and + provide all keyword arguments as additional identity + keys. Useful keys could be ``max_age`` or ``userdata``. + """ identifier = self._get_identifier(request) if identifier is None: return [] environ = request.environ - identity = {'repoze.who.userid':principal} + identity = kw + identity['repoze.who.userid'] = principal return identifier.remember(environ, identity) def forget(self, request): @@ -798,8 +808,6 @@ def encode_ip_timestamp(ip, timestamp): ts_chars = ''.join(map(chr, ts)) return bytes_(ip_chars + ts_chars) -EXPIRE = object() - class AuthTktCookieHelper(object): """ A helper class for use in third-party authentication policy @@ -830,55 +838,32 @@ class AuthTktCookieHelper(object): include_ip=False, timeout=None, reissue_time=None, max_age=None, http_only=False, path="/", wild_domain=True, hashalg='md5', parent_domain=False, domain=None): + + serializer = _SimpleSerializer() + + self.cookie_profile = CookieProfile( + cookie_name = cookie_name, + secure = secure, + max_age = max_age, + httponly = http_only, + path = path, + serializer=serializer + ) + self.secret = secret self.cookie_name = cookie_name - self.include_ip = include_ip self.secure = secure + self.include_ip = include_ip self.timeout = timeout self.reissue_time = reissue_time self.max_age = max_age - self.http_only = http_only - self.path = path self.wild_domain = wild_domain self.parent_domain = parent_domain self.domain = domain self.hashalg = hashalg - static_flags = [] - if self.secure: - static_flags.append('; Secure') - if self.http_only: - static_flags.append('; HttpOnly') - self.static_flags = "".join(static_flags) - - def _get_cookies(self, environ, value, max_age=None): - if max_age is EXPIRE: - max_age = "; Max-Age=0; Expires=Wed, 31-Dec-97 23:59:59 GMT" - elif max_age is not None: - later = datetime.datetime.utcnow() + datetime.timedelta( - seconds=int(max_age)) - # Wdy, DD-Mon-YY HH:MM:SS GMT - expires = later.strftime('%a, %d %b %Y %H:%M:%S GMT') - # the Expires header is *required* at least for IE7 (IE7 does - # not respect Max-Age) - max_age = "; Max-Age=%s; Expires=%s" % (max_age, expires) - else: - max_age = '' - - cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME')) - - # While Chrome, IE, and Firefox can cope, Opera (at least) cannot - # cope with a port number in the cookie domain when the URL it - # receives the cookie from does not also have that port number in it - # (e.g via a proxy). In the meantime, HTTP_HOST is sent with port - # number, and neither Firefox nor Chrome do anything with the - # information when it's provided in a cookie domain except strip it - # out. So we strip out any port number from the cookie domain - # aggressively to avoid problems. See also - # https://github.com/Pylons/pyramid/issues/131 - if ':' in cur_domain: - cur_domain = cur_domain.split(':', 1)[0] - + def _get_cookies(self, request, value, max_age=None): + cur_domain = request.domain domains = [] if self.domain: @@ -892,14 +877,15 @@ class AuthTktCookieHelper(object): if self.wild_domain: domains.append('.' + cur_domain) - cookies = [] - base_cookie = '%s="%s"; Path=%s%s%s' % (self.cookie_name, value, - self.path, max_age, self.static_flags) - for domain in domains: - domain = '; Domain=%s' % domain if domain is not None else '' - cookies.append(('Set-Cookie', '%s%s' % (base_cookie, domain))) + profile = self.cookie_profile(request) - return cookies + kw = {} + kw['domains'] = domains + if max_age is not None: + kw['max_age'] = max_age + + headers = profile.get_headers(value, **kw) + return headers def identify(self, request): """ Return a dictionary with authentication information, or ``None`` @@ -968,9 +954,8 @@ class AuthTktCookieHelper(object): def forget(self, request): """ Return a set of expires Set-Cookie headers, which will destroy any existing auth_tkt cookie when attached to a response""" - environ = request.environ request._authtkt_reissue_revoked = True - return self._get_cookies(environ, '', max_age=EXPIRE) + return self._get_cookies(request, None) def remember(self, request, userid, max_age=None, tokens=()): """ Return a set of Set-Cookie headers; when set into a response, @@ -1037,7 +1022,7 @@ class AuthTktCookieHelper(object): ) cookie_value = ticket.cookie_value() - return self._get_cookies(environ, cookie_value, max_age) + return self._get_cookies(request, cookie_value, max_age) @implementer(IAuthenticationPolicy) class SessionAuthenticationPolicy(CallbackAuthenticationPolicy): @@ -1196,3 +1181,11 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): except ValueError: # not enough values to unpack return None return username, password + +class _SimpleSerializer(object): + def loads(self, bstruct): + return native_(bstruct) + + def dumps(self, appstruct): + return bytes_(appstruct) + diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 19c47cbd9..32cf82fba 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -181,8 +181,11 @@ class Configurator( By default, ``default_permission`` is ``None``, meaning that view configurations which do not explicitly declare a permission will always be executable by entirely anonymous users (any - authorization policy in effect is ignored). See also - :ref:`setting_a_default_permission`. + authorization policy in effect is ignored). + + .. seealso:: + + See also :ref:`setting_a_default_permission`. If ``session_factory`` is passed, it should be an object which implements the :term:`session factory` interface. If a nondefault diff --git a/pyramid/config/factories.py b/pyramid/config/factories.py index 774125821..1990c377a 100644 --- a/pyramid/config/factories.py +++ b/pyramid/config/factories.py @@ -186,9 +186,9 @@ class FactoriesConfiguratorMixin(object): """ Add a property to the request object. .. deprecated:: 1.5 - :meth:`pyramid.config.Configurator.add_request_method` should be - used instead. (This method was docs-deprecated in 1.4 and - issues a real deprecation warning in 1.5). + :meth:`pyramid.config.Configurator.add_request_method` should be + used instead. (This method was docs-deprecated in 1.4 and + issues a real deprecation warning in 1.5). .. versionadded:: 1.3 """ diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 4fd207600..f1463b50b 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -249,22 +249,21 @@ class RoutesConfiguratorMixin(object): custom_predicates .. deprecated:: 1.5 - - This value should be a sequence of references to custom - predicate callables. Use custom predicates when no set of - predefined predicates does what you need. Custom predicates - can be combined with predefined predicates as necessary. - Each custom predicate callable should accept two arguments: - ``info`` and ``request`` and should return either ``True`` - or ``False`` after doing arbitrary evaluation of the info - and/or the request. If all custom and non-custom predicate - callables return ``True`` the associated route will be - considered viable for a given request. If any predicate - callable returns ``False``, route matching continues. Note - that the value ``info`` passed to a custom route predicate - is a dictionary containing matching information; see - :ref:`custom_route_predicates` for more information about - ``info``. + This value should be a sequence of references to custom + predicate callables. Use custom predicates when no set of + predefined predicates does what you need. Custom predicates + can be combined with predefined predicates as necessary. + Each custom predicate callable should accept two arguments: + ``info`` and ``request`` and should return either ``True`` + or ``False`` after doing arbitrary evaluation of the info + and/or the request. If all custom and non-custom predicate + callables return ``True`` the associated route will be + considered viable for a given request. If any predicate + callable returns ``False``, route matching continues. Note + that the value ``info`` passed to a custom route predicate + is a dictionary containing matching information; see + :ref:`custom_route_predicates` for more information about + ``info``. predicates diff --git a/pyramid/config/security.py b/pyramid/config/security.py index 6a1257b6a..81549cbfc 100644 --- a/pyramid/config/security.py +++ b/pyramid/config/security.py @@ -112,7 +112,9 @@ class SecurityConfiguratorMixin(object): permission is ignored, and the view is registered, making it available to all callers regardless of their credentials. - See also :ref:`setting_a_default_permission`. + .. seealso:: + + See also :ref:`setting_a_default_permission`. .. note:: diff --git a/pyramid/config/views.py b/pyramid/config/views.py index a3f885504..2f6c758ab 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -44,6 +44,11 @@ from pyramid.compat import ( is_nonstr_iter ) +from pyramid.encode import ( + quote_plus, + urlencode, +) + from pyramid.exceptions import ( ConfigurationError, PredicateMismatch, @@ -65,6 +70,8 @@ from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.static import static_view from pyramid.threadlocal import get_current_registry +from pyramid.url import parse_url_overrides + from pyramid.view import ( render_view_to_response, AppendSlashNotFoundViewFactory, @@ -872,13 +879,13 @@ class ViewsConfiguratorMixin(object): request_method - This value can be one of the strings ``GET``, ``POST``, ``PUT``, - ``DELETE``, or ``HEAD`` representing an HTTP ``REQUEST_METHOD``, or - a tuple containing one or more of these strings. A view - declaration with this argument ensures that the view will only be - called when the request's ``method`` attribute (aka the - ``REQUEST_METHOD`` of the WSGI environment) string matches a - supplied value. Note that use of ``GET`` also implies that the + This value can be either a strings (such as ``GET``, ``POST``, + ``PUT``, ``DELETE``, or ``HEAD``) representing an HTTP + ``REQUEST_METHOD``, or a tuple containing one or more of these + strings. A view declaration with this argument ensures that the + view will only be called when the ``method`` attribute of the + request (aka the ``REQUEST_METHOD`` of the WSGI environment) matches + a supplied value. Note that use of ``GET`` also implies that the view will respond to ``HEAD`` as of Pyramid 1.4. .. versionchanged:: 1.2 @@ -909,7 +916,7 @@ class ViewsConfiguratorMixin(object): A view declaration with this argument ensures that the view will only be called when the :term:`request` has key/value pairs in its :term:`matchdict` that equal those supplied in the predicate. - e.g. ``match_param="action=edit" would require the ``action`` + e.g. ``match_param="action=edit"`` would require the ``action`` parameter in the :term:`matchdict` match the right hand side of the expression (``edit``) for the view to "match" the current request. @@ -1028,18 +1035,20 @@ class ViewsConfiguratorMixin(object): custom_predicates - .. deprecated:: 1.5 - - This value should be a sequence of references to custom predicate - callables. Use custom predicates when no set of predefined - predicates do what you need. Custom predicates can be combined with - predefined predicates as necessary. Each custom predicate callable - should accept two arguments: ``context`` and ``request`` and should - return either ``True`` or ``False`` after doing arbitrary evaluation - of the context and/or the request. The ``predicates`` argument to - this method and the ability to register third-party view predicates - via :meth:`pyramid.config.Configurator.add_view_predicate` obsoletes - this argument, but it is kept around for backwards compatibility. + .. deprecated:: 1.5 + This value should be a sequence of references to custom + predicate callables. Use custom predicates when no set of + predefined predicates do what you need. Custom predicates + can be combined with predefined predicates as necessary. + Each custom predicate callable should accept two arguments: + ``context`` and ``request`` and should return either + ``True`` or ``False`` after doing arbitrary evaluation of + the context and/or the request. The ``predicates`` argument + to this method and the ability to register third-party view + predicates via + :meth:`pyramid.config.Configurator.add_view_predicate` + obsoletes this argument, but it is kept around for backwards + compatibility. predicates @@ -1737,7 +1746,9 @@ class ViewsConfiguratorMixin(object): the default view mapper to be used by all subsequent :term:`view configuration` registrations. - See also :ref:`using_a_view_mapper`. + .. seealso:: + + See also :ref:`using_a_view_mapper`. .. note:: @@ -1895,14 +1906,15 @@ class StaticURLInfo(object): kw['subpath'] = subpath return request.route_url(route_name, **kw) else: + app_url, scheme, host, port, qs, anchor = \ + parse_url_overrides(kw) parsed = url_parse(url) if not parsed.scheme: - # parsed.scheme is readonly, so we have to parse again - # to change the scheme, sigh. - url = urlparse.urlunparse(url_parse( - url, scheme=request.environ['wsgi.url_scheme'])) + url = urlparse.urlunparse(parsed._replace( + scheme=request.environ['wsgi.url_scheme'])) subpath = url_quote(subpath) - return urljoin(url, subpath) + result = urljoin(url, subpath) + return result + qs + anchor raise ValueError('No static URL definition matching %s' % path) diff --git a/pyramid/encode.py b/pyramid/encode.py index 9e190bc21..0be0107b3 100644 --- a/pyramid/encode.py +++ b/pyramid/encode.py @@ -3,11 +3,16 @@ from pyramid.compat import ( binary_type, is_nonstr_iter, url_quote as _url_quote, - url_quote_plus as quote_plus, # bw compat api (dnr) + url_quote_plus as _quote_plus, ) -def url_quote(s, safe=''): # bw compat api - return _url_quote(s, safe=safe) +def url_quote(val, safe=''): # bw compat api + cls = val.__class__ + if cls is text_type: + val = val.encode('utf-8') + elif cls is not binary_type: + val = str(val).encode('utf-8') + return _url_quote(val, safe=safe) def urlencode(query, doseq=True): """ @@ -47,28 +52,28 @@ def urlencode(query, doseq=True): prefix = '' for (k, v) in query: - k = _enc(k) + k = quote_plus(k) if is_nonstr_iter(v): for x in v: - x = _enc(x) + x = quote_plus(x) result += '%s%s=%s' % (prefix, k, x) prefix = '&' elif v is None: result += '%s%s=' % (prefix, k) else: - v = _enc(v) + v = quote_plus(v) result += '%s%s=%s' % (prefix, k, v) prefix = '&' return result -def _enc(val): +# bw compat api (dnr) +def quote_plus(val, safe=''): cls = val.__class__ if cls is text_type: val = val.encode('utf-8') elif cls is not binary_type: val = str(val).encode('utf-8') - return quote_plus(val) - + return _quote_plus(val, safe=safe) diff --git a/pyramid/events.py b/pyramid/events.py index 5179ab08a..97375638e 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -235,7 +235,9 @@ class BeforeRender(dict): For a description of the values present in the renderer globals dictionary, see :ref:`renderer_system_values`. - See also :class:`pyramid.interfaces.IBeforeRender`. + .. seealso:: + + See also :class:`pyramid.interfaces.IBeforeRender`. """ def __init__(self, system, rendering_val=None): dict.__init__(self, system) diff --git a/pyramid/i18n.py b/pyramid/i18n.py index cdedbc877..4c8f4b55d 100644 --- a/pyramid/i18n.py +++ b/pyramid/i18n.py @@ -25,10 +25,10 @@ from pyramid.threadlocal import get_current_registry class Localizer(object): """ An object providing translation and pluralizations related to - the current request's locale name. A - :class:`pyramid.i18n.Localizer` object is created using the - :func:`pyramid.i18n.get_localizer` function. - """ + the current request's locale name. A + :class:`pyramid.i18n.Localizer` object is created using the + :func:`pyramid.i18n.get_localizer` function. + """ def __init__(self, locale_name, translations): self.locale_name = locale_name self.translations = translations @@ -75,16 +75,16 @@ class Localizer(object): :term:`message identifier` objects as a singular/plural pair and an ``n`` value representing the number that appears in the message using gettext plural forms support. The ``singular`` - and ``plural`` objects passed may be translation strings or - unicode strings. ``n`` represents the number of elements. - ``domain`` is the translation domain to use to do the - pluralization, and ``mapping`` is the interpolation mapping - that should be used on the result. Note that if the objects - passed are translation strings, their domains and mappings are - ignored. The domain and mapping arguments must be used - instead. If the ``domain`` is not supplied, a default domain - is used (usually ``messages``). - + and ``plural`` objects should be unicode strings. There is no + reason to use translation string objects as arguments as all + metadata is ignored. + + ``n`` represents the number of elements. ``domain`` is the + translation domain to use to do the pluralization, and ``mapping`` + is the interpolation mapping that should be used on the result. If + the ``domain`` is not supplied, a default domain is used (usually + ``messages``). + Example:: num = 1 @@ -93,6 +93,19 @@ class Localizer(object): num, mapping={'num':num}) + If using the gettext plural support, which is required for + languages that have pluralisation rules other than n != 1, the + ``singular`` argument must be the message_id defined in the + translation file. The plural argument is not used in this case. + + Example:: + + num = 1 + translated = localizer.pluralize('item_plural', + '', + num, + mapping={'num':num}) + """ if self.pluralizer is None: @@ -107,7 +120,8 @@ def default_locale_negotiator(request): - First, the negotiator looks for the ``_LOCALE_`` attribute of the request object (possibly set by a view or a listener for an - :term:`event`). + :term:`event`). If the attribute exists and it is not ``None``, + its value will be used. - Then it looks for the ``request.params['_LOCALE_']`` value. @@ -144,9 +158,11 @@ def negotiate_locale_name(request): return locale_name def get_locale_name(request): - """ Return the :term:`locale name` associated with the current - request. Deprecated in favor of using request.locale_name directly as of - Pyramid 1.5.""" + """ + .. deprecated:: 1.5 + Use :attr:`pyramid.request.Request.locale_name` directly instead. + Return the :term:`locale name` associated with the current request. + """ return request.locale_name def make_localizer(current_locale_name, translation_directories): @@ -193,9 +209,12 @@ def make_localizer(current_locale_name, translation_directories): translations=translations) def get_localizer(request): - """ Retrieve a :class:`pyramid.i18n.Localizer` object - corresponding to the current request's locale name. Deprecated in favor - of using the ``request.localizer`` attribute directly as of Pyramid 1.5""" + """ + .. deprecated:: 1.5 + Use the :attr:`pyramid.request.Request.localizer` attribute directly + instead. Retrieve a :class:`pyramid.i18n.Localizer` object + corresponding to the current request's locale name. + """ return request.localizer class Translations(gettext.GNUTranslations, object): diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index cf651cf1e..75b9b1cb9 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -361,7 +361,9 @@ class IBeforeRender(IDict): def add_global(event): event['mykey'] = 'foo' - See also :ref:`beforerender_event`. + .. seealso:: + + See also :ref:`beforerender_event`. """ rendering_val = Attribute('The value returned by a view or passed to a ' '``render`` method for this rendering. ' @@ -767,10 +769,10 @@ class IResourceURL(Interface): ) class IContextURL(IResourceURL): - """ An adapter which deals with URLs related to a context. - + """ .. deprecated:: 1.3 - use IResourceURL instead. + An adapter which deals with URLs related to a context. Use + :class:`pyramid.interfaces.IResourceURL` instead. """ # this class subclasses IResourceURL because request.resource_url looks # for IResourceURL via queryAdapter. queryAdapter will find a deprecated diff --git a/pyramid/renderers.py b/pyramid/renderers.py index e90d07b38..108255ee4 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -341,7 +341,9 @@ class JSONP(JSON): .. versionadded:: 1.1 - See also: :ref:`jsonp_renderer`. + .. seealso:: + + See also :ref:`jsonp_renderer`. """ def __init__(self, param_name='callback', **kw): @@ -363,7 +365,7 @@ class JSONP(JSON): body = val else: ct = 'application/javascript' - body = '%s(%s)' % (callback, val) + body = '%s(%s);' % (callback, val) response = request.response if response.content_type == response.default_content_type: response.content_type = ct diff --git a/pyramid/request.py b/pyramid/request.py index 188e968ac..f8514066e 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -66,7 +66,9 @@ class CallbackMethodsMixin(object): will be propagated to the caller of the :app:`Pyramid` router application. - See also: :ref:`using_response_callbacks`. + .. seealso:: + + See also :ref:`using_response_callbacks`. """ callbacks = self.response_callbacks @@ -124,7 +126,9 @@ class CallbackMethodsMixin(object): They will be propagated to the caller of the :app:`Pyramid` router application. - See also: :ref:`using_finished_callbacks`. + .. seealso:: + + See also :ref:`using_finished_callbacks`. """ callbacks = self.finished_callbacks diff --git a/pyramid/router.py b/pyramid/router.py index 6239f3980..ba4f85b18 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -183,46 +183,16 @@ class Router(object): return response def invoke_subrequest(self, request, use_tweens=False): - """ - Obtain a response object from the Pyramid application based on + """Obtain a response object from the Pyramid application based on information in the ``request`` object provided. The ``request`` object must be an object that implements the Pyramid request interface (such as a :class:`pyramid.request.Request` instance). If ``use_tweens`` is ``True``, the request will be sent to the :term:`tween` in the tween stack closest to the request ingress. If ``use_tweens`` is ``False``, the request will be sent to the main - router handler, and no tweens will be invoked. This function also: + router handler, and no tweens will be invoked. - - manages the threadlocal stack (so that - :func:`~pyramid.threadlocal.get_current_request` and - :func:`~pyramid.threadlocal.get_current_registry` work during a - request) - - - Adds a ``registry`` attribute and a ``invoke_subrequest`` attribute - (a callable) to the request object it's handed. - - - sets request extensions (such as those added via - :meth:`~pyramid.config.Configurator.add_request_method` or - :meth:`~pyramid.config.Configurator.set_request_property`) on the - request it's passed. - - - causes a :class:`~pyramid.event.NewRequest` event to be sent at the - beginning of request processing. - - - causes a :class:`~pyramid.event.ContextFound` event to be sent - when a context resource is found. - - - Calls any :term:`response callback` functions defined within the - request's lifetime if a response is obtained from the Pyramid - application. - - - causes a :class:`~pyramid.event.NewResponse` event to be sent if a - response is obtained. - - - Calls any :term:`finished callback` functions defined within the - request's lifetime. - - See also :ref:`subrequest_chapter`. + See the API for pyramid.request for complete documentation. """ registry = self.registry has_listeners = self.registry.has_listeners diff --git a/pyramid/scaffolds/alchemy/+package+/static/favicon.ico b/pyramid/scaffolds/alchemy/+package+/static/favicon.ico Binary files differdeleted file mode 100644 index 71f837c9e..000000000 --- a/pyramid/scaffolds/alchemy/+package+/static/favicon.ico +++ /dev/null diff --git a/pyramid/scaffolds/alchemy/+package+/static/footerbg.png b/pyramid/scaffolds/alchemy/+package+/static/footerbg.png Binary files differdeleted file mode 100644 index 1fbc873da..000000000 --- a/pyramid/scaffolds/alchemy/+package+/static/footerbg.png +++ /dev/null diff --git a/pyramid/scaffolds/alchemy/+package+/static/headerbg.png b/pyramid/scaffolds/alchemy/+package+/static/headerbg.png Binary files differdeleted file mode 100644 index 0596f2020..000000000 --- a/pyramid/scaffolds/alchemy/+package+/static/headerbg.png +++ /dev/null diff --git a/pyramid/scaffolds/alchemy/+package+/static/ie6.css b/pyramid/scaffolds/alchemy/+package+/static/ie6.css deleted file mode 100644 index b7c8493d8..000000000 --- a/pyramid/scaffolds/alchemy/+package+/static/ie6.css +++ /dev/null @@ -1,8 +0,0 @@ -* html img, -* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", -this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", -this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) -);} -#wrap{display:table;height:100%} diff --git a/pyramid/scaffolds/alchemy/+package+/static/middlebg.png b/pyramid/scaffolds/alchemy/+package+/static/middlebg.png Binary files differdeleted file mode 100644 index 2369cfb7d..000000000 --- a/pyramid/scaffolds/alchemy/+package+/static/middlebg.png +++ /dev/null diff --git a/pyramid/scaffolds/alchemy/+package+/static/pylons.css b/pyramid/scaffolds/alchemy/+package+/static/pylons.css deleted file mode 100644 index 4b1c017cd..000000000 --- a/pyramid/scaffolds/alchemy/+package+/static/pylons.css +++ /dev/null @@ -1,372 +0,0 @@ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td -{ - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; /* 16px */ - vertical-align: baseline; - background: transparent; -} - -body -{ - line-height: 1; -} - -ol, ul -{ - list-style: none; -} - -blockquote, q -{ - quotes: none; -} - -blockquote:before, blockquote:after, q:before, q:after -{ - content: ''; - content: none; -} - -:focus -{ - outline: 0; -} - -ins -{ - text-decoration: none; -} - -del -{ - text-decoration: line-through; -} - -table -{ - border-collapse: collapse; - border-spacing: 0; -} - -sub -{ - vertical-align: sub; - font-size: smaller; - line-height: normal; -} - -sup -{ - vertical-align: super; - font-size: smaller; - line-height: normal; -} - -ul, menu, dir -{ - display: block; - list-style-type: disc; - margin: 1em 0; - padding-left: 40px; -} - -ol -{ - display: block; - list-style-type: decimal-leading-zero; - margin: 1em 0; - padding-left: 40px; -} - -li -{ - display: list-item; -} - -ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl -{ - margin-top: 0; - margin-bottom: 0; -} - -ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir -{ - list-style-type: circle; -} - -ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir -{ - list-style-type: square; -} - -.hidden -{ - display: none; -} - -p -{ - line-height: 1.5em; -} - -h1 -{ - font-size: 1.75em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h2 -{ - font-size: 1.5em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h3 -{ - font-size: 1.25em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h4 -{ - font-size: 1em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -html, body -{ - width: 100%; - height: 100%; -} - -body -{ - margin: 0; - padding: 0; - background-color: #fff; - position: relative; - font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; -} - -a -{ - color: #1b61d6; - text-decoration: none; -} - -a:hover -{ - color: #e88f00; - text-decoration: underline; -} - -body h1, body h2, body h3, body h4, body h5, body h6 -{ - font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; - font-weight: 400; - color: #373839; - font-style: normal; -} - -#wrap -{ - min-height: 100%; -} - -#header, #footer -{ - width: 100%; - color: #fff; - height: 40px; - position: absolute; - text-align: center; - line-height: 40px; - overflow: hidden; - font-size: 12px; - vertical-align: middle; -} - -#header -{ - background: #000; - top: 0; - font-size: 14px; -} - -#footer -{ - bottom: 0; - background: #000 url(footerbg.png) repeat-x 0 top; - position: relative; - margin-top: -40px; - clear: both; -} - -.header, .footer -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.wrapper -{ - width: 100%; -} - -#top, #top-small, #bottom -{ - width: 100%; -} - -#top -{ - color: #000; - height: 230px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#top-small -{ - color: #000; - height: 60px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#bottom -{ - color: #222; - background-color: #fff; -} - -.top, .top-small, .middle, .bottom -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.top -{ - padding-top: 40px; -} - -.top-small -{ - padding-top: 10px; -} - -#middle -{ - width: 100%; - height: 100px; - background: url(middlebg.png) repeat-x; - border-top: 2px solid #fff; - border-bottom: 2px solid #b2b2b2; -} - -.app-welcome -{ - margin-top: 25px; -} - -.app-name -{ - color: #000; - font-weight: 700; -} - -.bottom -{ - padding-top: 50px; -} - -#left -{ - width: 350px; - float: left; - padding-right: 25px; -} - -#right -{ - width: 350px; - float: right; - padding-left: 25px; -} - -.align-left -{ - text-align: left; -} - -.align-right -{ - text-align: right; -} - -.align-center -{ - text-align: center; -} - -ul.links -{ - margin: 0; - padding: 0; -} - -ul.links li -{ - list-style-type: none; - font-size: 14px; -} - -form -{ - border-style: none; -} - -fieldset -{ - border-style: none; -} - -input -{ - color: #222; - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 12px; - line-height: 16px; -} - -input[type=text], input[type=password] -{ - width: 205px; -} - -input[type=submit] -{ - background-color: #ddd; - font-weight: 700; -} - -/*Opera Fix*/ -body:before -{ - content: ""; - height: 100%; - float: left; - width: 0; - margin-top: -32767px; -} diff --git a/pyramid/scaffolds/alchemy/+package+/static/pyramid-16x16.png b/pyramid/scaffolds/alchemy/+package+/static/pyramid-16x16.png Binary files differnew file mode 100644 index 000000000..979203112 --- /dev/null +++ b/pyramid/scaffolds/alchemy/+package+/static/pyramid-16x16.png diff --git a/pyramid/scaffolds/alchemy/+package+/static/pyramid.png b/pyramid/scaffolds/alchemy/+package+/static/pyramid.png Binary files differindex 347e05549..4ab837be9 100644 --- a/pyramid/scaffolds/alchemy/+package+/static/pyramid.png +++ b/pyramid/scaffolds/alchemy/+package+/static/pyramid.png diff --git a/pyramid/scaffolds/alchemy/+package+/static/theme.css b/pyramid/scaffolds/alchemy/+package+/static/theme.css new file mode 100644 index 000000000..be50ad420 --- /dev/null +++ b/pyramid/scaffolds/alchemy/+package+/static/theme.css @@ -0,0 +1,152 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +body { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; + color: #ffffff; + background: #bc2131; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; +} +p { + font-weight: 300; +} +.font-normal { + font-weight: 400; +} +.font-semi-bold { + font-weight: 600; +} +.font-bold { + font-weight: 700; +} +.starter-template { + margin-top: 250px; +} +.starter-template .content { + margin-left: 10px; +} +.starter-template .content h1 { + margin-top: 10px; + font-size: 60px; +} +.starter-template .content h1 .smaller { + font-size: 40px; + color: #f2b7bd; +} +.starter-template .content .lead { + font-size: 25px; + color: #f2b7bd; +} +.starter-template .content .lead .font-normal { + color: #ffffff; +} +.starter-template .links { + float: right; + right: 0; + margin-top: 125px; +} +.starter-template .links ul { + display: block; + padding: 0; + margin: 0; +} +.starter-template .links ul li { + list-style: none; + display: inline; + margin: 0 10px; +} +.starter-template .links ul li:first-child { + margin-left: 0; +} +.starter-template .links ul li:last-child { + margin-right: 0; +} +.starter-template .links ul li.current-version { + color: #f2b7bd; + font-weight: 400; +} +.starter-template .links ul li a { + color: #ffffff; +} +.starter-template .links ul li a:hover { + text-decoration: underline; +} +.starter-template .links ul li .icon-muted { + color: #eb8b95; + margin-right: 5px; +} +.starter-template .links ul li:hover .icon-muted { + color: #ffffff; +} +.starter-template .copyright { + margin-top: 10px; + font-size: 0.9em; + color: #f2b7bd; + text-transform: lowercase; + float: right; + right: 0; +} +@media (max-width: 1199px) { + .starter-template .content h1 { + font-size: 45px; + } + .starter-template .content h1 .smaller { + font-size: 30px; + } + .starter-template .content .lead { + font-size: 20px; + } +} +@media (max-width: 991px) { + .starter-template { + margin-top: 0; + } + .starter-template .logo { + margin: 40px auto; + } + .starter-template .content { + margin-left: 0; + text-align: center; + } + .starter-template .content h1 { + margin-bottom: 20px; + } + .starter-template .links { + float: none; + text-align: center; + margin-top: 60px; + } + .starter-template .copyright { + float: none; + text-align: center; + } +} +@media (max-width: 767px) { + .starter-template .content h1 .smaller { + font-size: 25px; + display: block; + } + .starter-template .content .lead { + font-size: 16px; + } + .starter-template .links { + margin-top: 40px; + } + .starter-template .links ul li { + display: block; + margin: 0; + } + .starter-template .links ul li .icon-muted { + display: none; + } + .starter-template .copyright { + margin-top: 20px; + } +} diff --git a/pyramid/scaffolds/alchemy/+package+/static/theme.min.css b/pyramid/scaffolds/alchemy/+package+/static/theme.min.css new file mode 100644 index 000000000..2f924bcc5 --- /dev/null +++ b/pyramid/scaffolds/alchemy/+package+/static/theme.min.css @@ -0,0 +1 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
\ No newline at end of file diff --git a/pyramid/scaffolds/alchemy/+package+/static/transparent.gif b/pyramid/scaffolds/alchemy/+package+/static/transparent.gif Binary files differdeleted file mode 100644 index 0341802e5..000000000 --- a/pyramid/scaffolds/alchemy/+package+/static/transparent.gif +++ /dev/null diff --git a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl index 24651643c..76451f9b5 100644 --- a/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl @@ -1,73 +1,66 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> -<head> - <title>The Pyramid Web Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.static_url('{{package}}:static/favicon.ico')}" /> - <link rel="stylesheet" href="${request.static_url('{{package}}: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('{{package}}: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('{{package}}:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> - </div> - </div> - <div id="middle"> - <div class="middle align-center"> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid Web Framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Go" /> - </form> +<!DOCTYPE html> +<html lang="${request.locale_name}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('{{package}}:static/pyramid-16x16.png')}"> + + <title>Starter Template for The Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> + + <!-- Custom styles for this template --> + <link href="${request.static_url('{{package}}:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('{{package}}:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">starter template</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p> + </div> + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li class="current-version">Currently v1.5</li> + <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org">Docs</a></li> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> + </div> </div> - <div id="right" class="align-left"> - <h2>Pyramid links</h2> - <ul class="links"> - <li> - <a href="http://pylonsproject.org">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a> - </li> - <li> - <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> - </li> - </ul> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> </div> </div> </div> - </div> -</body> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script> + <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script> + </body> </html> diff --git a/pyramid/scaffolds/starter/+package+/static/favicon.ico b/pyramid/scaffolds/starter/+package+/static/favicon.ico Binary files differdeleted file mode 100644 index 71f837c9e..000000000 --- a/pyramid/scaffolds/starter/+package+/static/favicon.ico +++ /dev/null diff --git a/pyramid/scaffolds/starter/+package+/static/footerbg.png b/pyramid/scaffolds/starter/+package+/static/footerbg.png Binary files differdeleted file mode 100644 index 1fbc873da..000000000 --- a/pyramid/scaffolds/starter/+package+/static/footerbg.png +++ /dev/null diff --git a/pyramid/scaffolds/starter/+package+/static/headerbg.png b/pyramid/scaffolds/starter/+package+/static/headerbg.png Binary files differdeleted file mode 100644 index 0596f2020..000000000 --- a/pyramid/scaffolds/starter/+package+/static/headerbg.png +++ /dev/null diff --git a/pyramid/scaffolds/starter/+package+/static/ie6.css b/pyramid/scaffolds/starter/+package+/static/ie6.css deleted file mode 100644 index b7c8493d8..000000000 --- a/pyramid/scaffolds/starter/+package+/static/ie6.css +++ /dev/null @@ -1,8 +0,0 @@ -* html img, -* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", -this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", -this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) -);} -#wrap{display:table;height:100%} diff --git a/pyramid/scaffolds/starter/+package+/static/middlebg.png b/pyramid/scaffolds/starter/+package+/static/middlebg.png Binary files differdeleted file mode 100644 index 2369cfb7d..000000000 --- a/pyramid/scaffolds/starter/+package+/static/middlebg.png +++ /dev/null diff --git a/pyramid/scaffolds/starter/+package+/static/pylons.css b/pyramid/scaffolds/starter/+package+/static/pylons.css deleted file mode 100644 index 4b1c017cd..000000000 --- a/pyramid/scaffolds/starter/+package+/static/pylons.css +++ /dev/null @@ -1,372 +0,0 @@ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td -{ - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; /* 16px */ - vertical-align: baseline; - background: transparent; -} - -body -{ - line-height: 1; -} - -ol, ul -{ - list-style: none; -} - -blockquote, q -{ - quotes: none; -} - -blockquote:before, blockquote:after, q:before, q:after -{ - content: ''; - content: none; -} - -:focus -{ - outline: 0; -} - -ins -{ - text-decoration: none; -} - -del -{ - text-decoration: line-through; -} - -table -{ - border-collapse: collapse; - border-spacing: 0; -} - -sub -{ - vertical-align: sub; - font-size: smaller; - line-height: normal; -} - -sup -{ - vertical-align: super; - font-size: smaller; - line-height: normal; -} - -ul, menu, dir -{ - display: block; - list-style-type: disc; - margin: 1em 0; - padding-left: 40px; -} - -ol -{ - display: block; - list-style-type: decimal-leading-zero; - margin: 1em 0; - padding-left: 40px; -} - -li -{ - display: list-item; -} - -ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl -{ - margin-top: 0; - margin-bottom: 0; -} - -ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir -{ - list-style-type: circle; -} - -ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir -{ - list-style-type: square; -} - -.hidden -{ - display: none; -} - -p -{ - line-height: 1.5em; -} - -h1 -{ - font-size: 1.75em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h2 -{ - font-size: 1.5em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h3 -{ - font-size: 1.25em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h4 -{ - font-size: 1em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -html, body -{ - width: 100%; - height: 100%; -} - -body -{ - margin: 0; - padding: 0; - background-color: #fff; - position: relative; - font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; -} - -a -{ - color: #1b61d6; - text-decoration: none; -} - -a:hover -{ - color: #e88f00; - text-decoration: underline; -} - -body h1, body h2, body h3, body h4, body h5, body h6 -{ - font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; - font-weight: 400; - color: #373839; - font-style: normal; -} - -#wrap -{ - min-height: 100%; -} - -#header, #footer -{ - width: 100%; - color: #fff; - height: 40px; - position: absolute; - text-align: center; - line-height: 40px; - overflow: hidden; - font-size: 12px; - vertical-align: middle; -} - -#header -{ - background: #000; - top: 0; - font-size: 14px; -} - -#footer -{ - bottom: 0; - background: #000 url(footerbg.png) repeat-x 0 top; - position: relative; - margin-top: -40px; - clear: both; -} - -.header, .footer -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.wrapper -{ - width: 100%; -} - -#top, #top-small, #bottom -{ - width: 100%; -} - -#top -{ - color: #000; - height: 230px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#top-small -{ - color: #000; - height: 60px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#bottom -{ - color: #222; - background-color: #fff; -} - -.top, .top-small, .middle, .bottom -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.top -{ - padding-top: 40px; -} - -.top-small -{ - padding-top: 10px; -} - -#middle -{ - width: 100%; - height: 100px; - background: url(middlebg.png) repeat-x; - border-top: 2px solid #fff; - border-bottom: 2px solid #b2b2b2; -} - -.app-welcome -{ - margin-top: 25px; -} - -.app-name -{ - color: #000; - font-weight: 700; -} - -.bottom -{ - padding-top: 50px; -} - -#left -{ - width: 350px; - float: left; - padding-right: 25px; -} - -#right -{ - width: 350px; - float: right; - padding-left: 25px; -} - -.align-left -{ - text-align: left; -} - -.align-right -{ - text-align: right; -} - -.align-center -{ - text-align: center; -} - -ul.links -{ - margin: 0; - padding: 0; -} - -ul.links li -{ - list-style-type: none; - font-size: 14px; -} - -form -{ - border-style: none; -} - -fieldset -{ - border-style: none; -} - -input -{ - color: #222; - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 12px; - line-height: 16px; -} - -input[type=text], input[type=password] -{ - width: 205px; -} - -input[type=submit] -{ - background-color: #ddd; - font-weight: 700; -} - -/*Opera Fix*/ -body:before -{ - content: ""; - height: 100%; - float: left; - width: 0; - margin-top: -32767px; -} diff --git a/pyramid/scaffolds/starter/+package+/static/pyramid-16x16.png b/pyramid/scaffolds/starter/+package+/static/pyramid-16x16.png Binary files differnew file mode 100644 index 000000000..979203112 --- /dev/null +++ b/pyramid/scaffolds/starter/+package+/static/pyramid-16x16.png diff --git a/pyramid/scaffolds/starter/+package+/static/pyramid.png b/pyramid/scaffolds/starter/+package+/static/pyramid.png Binary files differindex 347e05549..4ab837be9 100644 --- a/pyramid/scaffolds/starter/+package+/static/pyramid.png +++ b/pyramid/scaffolds/starter/+package+/static/pyramid.png diff --git a/pyramid/scaffolds/starter/+package+/static/theme.css b/pyramid/scaffolds/starter/+package+/static/theme.css new file mode 100644 index 000000000..be50ad420 --- /dev/null +++ b/pyramid/scaffolds/starter/+package+/static/theme.css @@ -0,0 +1,152 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +body { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; + color: #ffffff; + background: #bc2131; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; +} +p { + font-weight: 300; +} +.font-normal { + font-weight: 400; +} +.font-semi-bold { + font-weight: 600; +} +.font-bold { + font-weight: 700; +} +.starter-template { + margin-top: 250px; +} +.starter-template .content { + margin-left: 10px; +} +.starter-template .content h1 { + margin-top: 10px; + font-size: 60px; +} +.starter-template .content h1 .smaller { + font-size: 40px; + color: #f2b7bd; +} +.starter-template .content .lead { + font-size: 25px; + color: #f2b7bd; +} +.starter-template .content .lead .font-normal { + color: #ffffff; +} +.starter-template .links { + float: right; + right: 0; + margin-top: 125px; +} +.starter-template .links ul { + display: block; + padding: 0; + margin: 0; +} +.starter-template .links ul li { + list-style: none; + display: inline; + margin: 0 10px; +} +.starter-template .links ul li:first-child { + margin-left: 0; +} +.starter-template .links ul li:last-child { + margin-right: 0; +} +.starter-template .links ul li.current-version { + color: #f2b7bd; + font-weight: 400; +} +.starter-template .links ul li a { + color: #ffffff; +} +.starter-template .links ul li a:hover { + text-decoration: underline; +} +.starter-template .links ul li .icon-muted { + color: #eb8b95; + margin-right: 5px; +} +.starter-template .links ul li:hover .icon-muted { + color: #ffffff; +} +.starter-template .copyright { + margin-top: 10px; + font-size: 0.9em; + color: #f2b7bd; + text-transform: lowercase; + float: right; + right: 0; +} +@media (max-width: 1199px) { + .starter-template .content h1 { + font-size: 45px; + } + .starter-template .content h1 .smaller { + font-size: 30px; + } + .starter-template .content .lead { + font-size: 20px; + } +} +@media (max-width: 991px) { + .starter-template { + margin-top: 0; + } + .starter-template .logo { + margin: 40px auto; + } + .starter-template .content { + margin-left: 0; + text-align: center; + } + .starter-template .content h1 { + margin-bottom: 20px; + } + .starter-template .links { + float: none; + text-align: center; + margin-top: 60px; + } + .starter-template .copyright { + float: none; + text-align: center; + } +} +@media (max-width: 767px) { + .starter-template .content h1 .smaller { + font-size: 25px; + display: block; + } + .starter-template .content .lead { + font-size: 16px; + } + .starter-template .links { + margin-top: 40px; + } + .starter-template .links ul li { + display: block; + margin: 0; + } + .starter-template .links ul li .icon-muted { + display: none; + } + .starter-template .copyright { + margin-top: 20px; + } +} diff --git a/pyramid/scaffolds/starter/+package+/static/theme.min.css b/pyramid/scaffolds/starter/+package+/static/theme.min.css new file mode 100644 index 000000000..2f924bcc5 --- /dev/null +++ b/pyramid/scaffolds/starter/+package+/static/theme.min.css @@ -0,0 +1 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
\ No newline at end of file diff --git a/pyramid/scaffolds/starter/+package+/static/transparent.gif b/pyramid/scaffolds/starter/+package+/static/transparent.gif Binary files differdeleted file mode 100644 index 0341802e5..000000000 --- a/pyramid/scaffolds/starter/+package+/static/transparent.gif +++ /dev/null diff --git a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl index c9a211935..76451f9b5 100644 --- a/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl +++ b/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl @@ -1,73 +1,66 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> -<head> - <title>The Pyramid Web Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.static_url('{{package}}:static/favicon.ico')}" /> - <link rel="stylesheet" href="${request.static_url('{{package}}: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('{{package}}: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('{{package}}:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> - </div> - </div> - <div id="middle"> - <div class="middle align-center"> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid Web Framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Go" /> - </form> +<!DOCTYPE html> +<html lang="${request.locale_name}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('{{package}}:static/pyramid-16x16.png')}"> + + <title>Starter Template for The Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> + + <!-- Custom styles for this template --> + <link href="${request.static_url('{{package}}:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('{{package}}:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">starter template</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p> + </div> + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li class="current-version">Currently v1.5</li> + <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org">Docs</a></li> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> + </div> </div> - <div id="right" class="align-left"> - <h2>Pyramid links</h2> - <ul class="links"> - <li> - <a href="http://pylonsproject.org">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a> - </li> - <li> - <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> - </li> - </ul> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> </div> </div> </div> - </div> -</body> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script> + <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script> + </body> </html> diff --git a/pyramid/scaffolds/zodb/+package+/static/favicon.ico b/pyramid/scaffolds/zodb/+package+/static/favicon.ico Binary files differdeleted file mode 100644 index 71f837c9e..000000000 --- a/pyramid/scaffolds/zodb/+package+/static/favicon.ico +++ /dev/null diff --git a/pyramid/scaffolds/zodb/+package+/static/footerbg.png b/pyramid/scaffolds/zodb/+package+/static/footerbg.png Binary files differdeleted file mode 100644 index 1fbc873da..000000000 --- a/pyramid/scaffolds/zodb/+package+/static/footerbg.png +++ /dev/null diff --git a/pyramid/scaffolds/zodb/+package+/static/headerbg.png b/pyramid/scaffolds/zodb/+package+/static/headerbg.png Binary files differdeleted file mode 100644 index 0596f2020..000000000 --- a/pyramid/scaffolds/zodb/+package+/static/headerbg.png +++ /dev/null diff --git a/pyramid/scaffolds/zodb/+package+/static/ie6.css b/pyramid/scaffolds/zodb/+package+/static/ie6.css deleted file mode 100644 index b7c8493d8..000000000 --- a/pyramid/scaffolds/zodb/+package+/static/ie6.css +++ /dev/null @@ -1,8 +0,0 @@ -* html img, -* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", -this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), -this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", -this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) -);} -#wrap{display:table;height:100%} diff --git a/pyramid/scaffolds/zodb/+package+/static/middlebg.png b/pyramid/scaffolds/zodb/+package+/static/middlebg.png Binary files differdeleted file mode 100644 index 2369cfb7d..000000000 --- a/pyramid/scaffolds/zodb/+package+/static/middlebg.png +++ /dev/null diff --git a/pyramid/scaffolds/zodb/+package+/static/pylons.css b/pyramid/scaffolds/zodb/+package+/static/pylons.css deleted file mode 100644 index 4b1c017cd..000000000 --- a/pyramid/scaffolds/zodb/+package+/static/pylons.css +++ /dev/null @@ -1,372 +0,0 @@ -html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td -{ - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-size: 100%; /* 16px */ - vertical-align: baseline; - background: transparent; -} - -body -{ - line-height: 1; -} - -ol, ul -{ - list-style: none; -} - -blockquote, q -{ - quotes: none; -} - -blockquote:before, blockquote:after, q:before, q:after -{ - content: ''; - content: none; -} - -:focus -{ - outline: 0; -} - -ins -{ - text-decoration: none; -} - -del -{ - text-decoration: line-through; -} - -table -{ - border-collapse: collapse; - border-spacing: 0; -} - -sub -{ - vertical-align: sub; - font-size: smaller; - line-height: normal; -} - -sup -{ - vertical-align: super; - font-size: smaller; - line-height: normal; -} - -ul, menu, dir -{ - display: block; - list-style-type: disc; - margin: 1em 0; - padding-left: 40px; -} - -ol -{ - display: block; - list-style-type: decimal-leading-zero; - margin: 1em 0; - padding-left: 40px; -} - -li -{ - display: list-item; -} - -ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl -{ - margin-top: 0; - margin-bottom: 0; -} - -ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir -{ - list-style-type: circle; -} - -ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir -{ - list-style-type: square; -} - -.hidden -{ - display: none; -} - -p -{ - line-height: 1.5em; -} - -h1 -{ - font-size: 1.75em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h2 -{ - font-size: 1.5em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h3 -{ - font-size: 1.25em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -h4 -{ - font-size: 1em; - line-height: 1.7em; - font-family: helvetica, verdana; -} - -html, body -{ - width: 100%; - height: 100%; -} - -body -{ - margin: 0; - padding: 0; - background-color: #fff; - position: relative; - font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; -} - -a -{ - color: #1b61d6; - text-decoration: none; -} - -a:hover -{ - color: #e88f00; - text-decoration: underline; -} - -body h1, body h2, body h3, body h4, body h5, body h6 -{ - font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; - font-weight: 400; - color: #373839; - font-style: normal; -} - -#wrap -{ - min-height: 100%; -} - -#header, #footer -{ - width: 100%; - color: #fff; - height: 40px; - position: absolute; - text-align: center; - line-height: 40px; - overflow: hidden; - font-size: 12px; - vertical-align: middle; -} - -#header -{ - background: #000; - top: 0; - font-size: 14px; -} - -#footer -{ - bottom: 0; - background: #000 url(footerbg.png) repeat-x 0 top; - position: relative; - margin-top: -40px; - clear: both; -} - -.header, .footer -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.wrapper -{ - width: 100%; -} - -#top, #top-small, #bottom -{ - width: 100%; -} - -#top -{ - color: #000; - height: 230px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#top-small -{ - color: #000; - height: 60px; - background: #fff url(headerbg.png) repeat-x 0 top; - position: relative; -} - -#bottom -{ - color: #222; - background-color: #fff; -} - -.top, .top-small, .middle, .bottom -{ - width: 750px; - margin-right: auto; - margin-left: auto; -} - -.top -{ - padding-top: 40px; -} - -.top-small -{ - padding-top: 10px; -} - -#middle -{ - width: 100%; - height: 100px; - background: url(middlebg.png) repeat-x; - border-top: 2px solid #fff; - border-bottom: 2px solid #b2b2b2; -} - -.app-welcome -{ - margin-top: 25px; -} - -.app-name -{ - color: #000; - font-weight: 700; -} - -.bottom -{ - padding-top: 50px; -} - -#left -{ - width: 350px; - float: left; - padding-right: 25px; -} - -#right -{ - width: 350px; - float: right; - padding-left: 25px; -} - -.align-left -{ - text-align: left; -} - -.align-right -{ - text-align: right; -} - -.align-center -{ - text-align: center; -} - -ul.links -{ - margin: 0; - padding: 0; -} - -ul.links li -{ - list-style-type: none; - font-size: 14px; -} - -form -{ - border-style: none; -} - -fieldset -{ - border-style: none; -} - -input -{ - color: #222; - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 12px; - line-height: 16px; -} - -input[type=text], input[type=password] -{ - width: 205px; -} - -input[type=submit] -{ - background-color: #ddd; - font-weight: 700; -} - -/*Opera Fix*/ -body:before -{ - content: ""; - height: 100%; - float: left; - width: 0; - margin-top: -32767px; -} diff --git a/pyramid/scaffolds/zodb/+package+/static/pyramid-16x16.png b/pyramid/scaffolds/zodb/+package+/static/pyramid-16x16.png Binary files differnew file mode 100644 index 000000000..979203112 --- /dev/null +++ b/pyramid/scaffolds/zodb/+package+/static/pyramid-16x16.png diff --git a/pyramid/scaffolds/zodb/+package+/static/pyramid.png b/pyramid/scaffolds/zodb/+package+/static/pyramid.png Binary files differindex 347e05549..4ab837be9 100644 --- a/pyramid/scaffolds/zodb/+package+/static/pyramid.png +++ b/pyramid/scaffolds/zodb/+package+/static/pyramid.png diff --git a/pyramid/scaffolds/zodb/+package+/static/theme.css b/pyramid/scaffolds/zodb/+package+/static/theme.css new file mode 100644 index 000000000..be50ad420 --- /dev/null +++ b/pyramid/scaffolds/zodb/+package+/static/theme.css @@ -0,0 +1,152 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); +body { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; + color: #ffffff; + background: #bc2131; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 300; +} +p { + font-weight: 300; +} +.font-normal { + font-weight: 400; +} +.font-semi-bold { + font-weight: 600; +} +.font-bold { + font-weight: 700; +} +.starter-template { + margin-top: 250px; +} +.starter-template .content { + margin-left: 10px; +} +.starter-template .content h1 { + margin-top: 10px; + font-size: 60px; +} +.starter-template .content h1 .smaller { + font-size: 40px; + color: #f2b7bd; +} +.starter-template .content .lead { + font-size: 25px; + color: #f2b7bd; +} +.starter-template .content .lead .font-normal { + color: #ffffff; +} +.starter-template .links { + float: right; + right: 0; + margin-top: 125px; +} +.starter-template .links ul { + display: block; + padding: 0; + margin: 0; +} +.starter-template .links ul li { + list-style: none; + display: inline; + margin: 0 10px; +} +.starter-template .links ul li:first-child { + margin-left: 0; +} +.starter-template .links ul li:last-child { + margin-right: 0; +} +.starter-template .links ul li.current-version { + color: #f2b7bd; + font-weight: 400; +} +.starter-template .links ul li a { + color: #ffffff; +} +.starter-template .links ul li a:hover { + text-decoration: underline; +} +.starter-template .links ul li .icon-muted { + color: #eb8b95; + margin-right: 5px; +} +.starter-template .links ul li:hover .icon-muted { + color: #ffffff; +} +.starter-template .copyright { + margin-top: 10px; + font-size: 0.9em; + color: #f2b7bd; + text-transform: lowercase; + float: right; + right: 0; +} +@media (max-width: 1199px) { + .starter-template .content h1 { + font-size: 45px; + } + .starter-template .content h1 .smaller { + font-size: 30px; + } + .starter-template .content .lead { + font-size: 20px; + } +} +@media (max-width: 991px) { + .starter-template { + margin-top: 0; + } + .starter-template .logo { + margin: 40px auto; + } + .starter-template .content { + margin-left: 0; + text-align: center; + } + .starter-template .content h1 { + margin-bottom: 20px; + } + .starter-template .links { + float: none; + text-align: center; + margin-top: 60px; + } + .starter-template .copyright { + float: none; + text-align: center; + } +} +@media (max-width: 767px) { + .starter-template .content h1 .smaller { + font-size: 25px; + display: block; + } + .starter-template .content .lead { + font-size: 16px; + } + .starter-template .links { + margin-top: 40px; + } + .starter-template .links ul li { + display: block; + margin: 0; + } + .starter-template .links ul li .icon-muted { + display: none; + } + .starter-template .copyright { + margin-top: 20px; + } +} diff --git a/pyramid/scaffolds/zodb/+package+/static/theme.min.css b/pyramid/scaffolds/zodb/+package+/static/theme.min.css new file mode 100644 index 000000000..2f924bcc5 --- /dev/null +++ b/pyramid/scaffolds/zodb/+package+/static/theme.min.css @@ -0,0 +1 @@ +@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
\ No newline at end of file diff --git a/pyramid/scaffolds/zodb/+package+/static/transparent.gif b/pyramid/scaffolds/zodb/+package+/static/transparent.gif Binary files differdeleted file mode 100644 index 0341802e5..000000000 --- a/pyramid/scaffolds/zodb/+package+/static/transparent.gif +++ /dev/null diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt deleted file mode 100644 index 5a2ce2bd7..000000000 --- a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt +++ /dev/null @@ -1,73 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> -<head> - <title>The Pyramid Web Framework</title> - <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> - <meta name="keywords" content="python web application" /> - <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="/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="/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="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> - </div> - </div> - <div id="middle"> - <div class="middle align-center"> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid Web Framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h2>Search documentation</h2> - <form method="get" action="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Go" /> - </form> - </div> - <div id="right" class="align-left"> - <h2>Pyramid links</h2> - <ul class="links"> - <li> - <a href="http://pylonsproject.org">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#reference-material">API Documentation</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#tutorials">Tutorials</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#detailed-change-history">Change History</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#sample-applications">Sample Applications</a> - </li> - <li> - <a href="http://docs.pylonsproject.org/projects/pyramid/en/1.4-branch/#support-and-development">Support and Development</a> - </li> - <li> - <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> - </li> - </ul> - </div> - </div> - </div> - </div> -</body> -</html> diff --git a/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl new file mode 100644 index 000000000..76451f9b5 --- /dev/null +++ b/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html lang="${request.locale_name}"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="description" content="pyramid web application"> + <meta name="author" content="Pylons Project"> + <link rel="shortcut icon" href="${request.static_url('{{package}}:static/pyramid-16x16.png')}"> + + <title>Starter Template for The Pyramid Web Framework</title> + + <!-- Bootstrap core CSS --> + <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> + + <!-- Custom styles for this template --> + <link href="${request.static_url('{{package}}:static/theme.css')}" rel="stylesheet"> + + <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> + <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> + <![endif]--> + </head> + + <body> + + <div class="starter-template"> + <div class="container"> + <div class="row"> + <div class="col-md-2"> + <img class="logo img-responsive" src="${request.static_url('{{package}}:static/pyramid.png')}" alt="pyramid web framework"> + </div> + <div class="col-md-10"> + <div class="content"> + <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">starter template</span></h1> + <p class="lead">Welcome to <span class="font-normal">${project}</span>, an application generated by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p> + </div> + </div> + </div> + <div class="row"> + <div class="links"> + <ul> + <li class="current-version">Currently v1.5</li> + <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org">Docs</a></li> + <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li> + <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li> + <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li> + </div> + </div> + <div class="row"> + <div class="copyright"> + Copyright © Pylons Project + </div> + </div> + </div> + </div> + + + <!-- Bootstrap core JavaScript + ================================================== --> + <!-- Placed at the end of the document so the pages load faster --> + <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script> + <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script> + </body> +</html> diff --git a/pyramid/scripts/pserve.py b/pyramid/scripts/pserve.py index 03cbc23ab..ea125a0dd 100644 --- a/pyramid/scripts/pserve.py +++ b/pyramid/scripts/pserve.py @@ -811,7 +811,7 @@ class Monitor(object): # pragma: no cover print( "Error calling reloader callback %r:" % file_callback) traceback.print_exc() - for module in sys.modules.values(): + for module in list(sys.modules.values()): try: filename = module.__file__ except (AttributeError, ImportError): diff --git a/pyramid/scripts/pshell.py b/pyramid/scripts/pshell.py index dd09bf457..12b078677 100644 --- a/pyramid/scripts/pshell.py +++ b/pyramid/scripts/pshell.py @@ -153,16 +153,12 @@ class PShellCommand(object): shell = None user_shell = self.options.python_shell.lower() if not user_shell: - shell = self.make_ipython_v0_11_shell() - if shell is None: - shell = self.make_ipython_v0_10_shell() + shell = self.make_ipython_shell() if shell is None: shell = self.make_bpython_shell() elif user_shell == 'ipython': - shell = self.make_ipython_v0_11_shell() - if shell is None: - shell = self.make_ipython_v0_10_shell() + shell = self.make_ipython_shell() elif user_shell == 'bpython': shell = self.make_bpython_shell() @@ -191,6 +187,27 @@ class PShellCommand(object): BPShell(locals_=env, banner=help + '\n') return shell + def make_ipython_shell(self): + shell = self.make_ipython_v1_1_shell() + if shell is None: + shell = self.make_ipython_v0_11_shell() + if shell is None: + shell = self.make_ipython_v0_10_shell() + return shell + + def make_ipython_v1_1_shell(self, IPShellFactory=None): + if IPShellFactory is None: # pragma: no cover + try: + from IPython.terminal.embed import ( + InteractiveShellEmbed) + IPShellFactory = InteractiveShellEmbed + except ImportError: + return None + def shell(env, help): + IPShell = IPShellFactory(banner2=help + '\n', user_ns=env) + IPShell() + return shell + def make_ipython_v0_11_shell(self, IPShellFactory=None): if IPShellFactory is None: # pragma: no cover try: diff --git a/pyramid/security.py b/pyramid/security.py index 58fa9332a..c98d4e6cc 100644 --- a/pyramid/security.py +++ b/pyramid/security.py @@ -44,15 +44,15 @@ def _get_authentication_policy(request): def has_permission(permission, context, request): """ - A function that calls - :meth:`pyramid.request.Request.has_permission` and returns its result. + A function that calls :meth:`pyramid.request.Request.has_permission` + and returns its result. .. deprecated:: 1.5 - Use :meth:`pyramid.request.Request.has_permission` instead. + Use :meth:`pyramid.request.Request.has_permission` instead. .. versionchanged:: 1.5a3 - If context is None, then attempt to use the context attribute - of self, if not set then the AttributeError is propergated. + If context is None, then attempt to use the context attribute of self; + if not set, then the AttributeError is propagated. """ return request.has_permission(permission, context) @@ -87,7 +87,7 @@ def unauthenticated_userid(request): :attr:`pyramid.request.Request.unauthenticated_userid`. .. deprecated:: 1.5 - Use :attr:`pyramid.request.Request.unauthenticated_userid` instead. + Use :attr:`pyramid.request.Request.unauthenticated_userid` instead. """ return request.unauthenticated_userid @@ -104,7 +104,7 @@ def effective_principals(request): :attr:`pyramid.request.Request.effective_principals`. .. deprecated:: 1.5 - Use :attr:`pyramid.request.Request.effective_principals` instead. + Use :attr:`pyramid.request.Request.effective_principals` instead. """ return request.effective_principals @@ -160,9 +160,6 @@ def forget(request): If no :term:`authentication policy` is in use, this function will always return an empty sequence. - - .. deprecated:: 1.5 - Use :meth:`pyramid.request.Request.get_logout_headers` instead. """ policy = _get_authentication_policy(request) if policy is None: @@ -343,10 +340,9 @@ class AuthenticationAPIMixin(object): @property def effective_principals(self): """ Return the list of 'effective' :term:`principal` identifiers - for the ``request``. This will include the userid of the - currently authenticated user if a user is currently - authenticated. If no :term:`authentication policy` is in effect, - this will return an empty sequence. + for the ``request``. If no :term:`authentication policy` is in effect, + this will return a one-element list containing the + :data:`pyramid.security.Everyone` principal. .. versionadded:: 1.5 """ @@ -354,7 +350,7 @@ class AuthenticationAPIMixin(object): if policy is None: return [Everyone] return policy.effective_principals(self) - + class AuthorizationAPIMixin(object): def has_permission(self, permission, context=None): diff --git a/pyramid/session.py b/pyramid/session.py index d3a4113b9..56d99e9de 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -8,6 +8,8 @@ import time from zope.deprecation import deprecated from zope.interface import implementer +from webob.cookies import SignedSerializer + from pyramid.compat import ( pickle, PY3, @@ -25,8 +27,9 @@ def manage_accessed(wrapped): method is called.""" def accessed(session, *arg, **kw): session.accessed = now = int(time.time()) - if now - session.renewed > session._reissue_time: - session.changed() + if session._reissue_time is not None: + if now - session.renewed > session._reissue_time: + session.changed() return wrapped(session, *arg, **kw) accessed.__doc__ = wrapped.__doc__ return accessed @@ -119,9 +122,17 @@ def check_csrf_token(request, return False return True +class PickleSerializer(object): + """ A Webob cookie serializer that uses the pickle protocol to dump Python + data to bytes.""" + def loads(self, bstruct): + return pickle.loads(bstruct) + + def dumps(self, appstruct): + return pickle.dumps(appstruct, pickle.HIGHEST_PROTOCOL) + def BaseCookieSessionFactory( - serialize, - deserialize, + serializer, cookie_name='session', max_age=None, path='/', @@ -154,13 +165,11 @@ def BaseCookieSessionFactory( Parameters: - ``serialize`` - A callable accepting a Python object and returning a bytestring. A - ``ValueError`` should be raised for malformed inputs. - - ``deserialize`` - A callable accepting a bytestring and returning a Python object. A - ``ValueError`` should be raised for malformed inputs. + ``serializer`` + An object with two methods: ``loads`` and ``dumps``. The ``loads`` + method should accept bytes and return a Python object. The ``dumps`` + method should accept a Python object and return bytes. A ``ValueError`` + should be raised for malformed inputs. ``cookie_name`` The name of the cookie used for sessioning. Default: ``'session'``. @@ -184,14 +193,17 @@ def BaseCookieSessionFactory( ``timeout`` A number of seconds of inactivity before a session times out. If - ``None`` then the cookie never expires. Default: 1200. + ``None`` then the cookie never expires. This lifetime only applies + to the *value* within the cookie. Meaning that if the cookie expires + due to a lower ``max_age``, then this setting has no effect. + Default: ``1200``. ``reissue_time`` The number of seconds that must pass before the cookie is automatically reissued as the result of a request which accesses the session. The duration is measured as the number of seconds since the last session cookie was issued and 'now'. If this value is ``0``, a new cookie - will be reissued on every request accesses the session. If ``None`` + will be reissued on every request accessing the session. If ``None`` then the cookie's lifetime will never be extended. A good rule of thumb: if you want auto-expired cookies based on @@ -238,24 +250,31 @@ def BaseCookieSessionFactory( cookieval = request.cookies.get(self._cookie_name) if cookieval is not None: try: - value = deserialize(bytes_(cookieval)) + value = serializer.loads(bytes_(cookieval)) except ValueError: # the cookie failed to deserialize, dropped value = None if value is not None: try: - renewed, created, state = value + # since the value is not necessarily signed, we have + # to unpack it a little carefully + rval, cval, sval = value + renewed = float(rval) + created = float(cval) + state = sval new = False - if now - renewed > self._timeout: - # expire the session because it was not renewed - # before the timeout threshold - state = {} - except TypeError: + except (TypeError, ValueError): # value failed to unpack properly or renewed was not # a numeric type so we'll fail deserialization here state = {} + if self._timeout is not None: + if now - renewed > self._timeout: + # expire the session because it was not renewed + # before the timeout threshold + state = {} + self.created = created self.accessed = renewed self.renewed = renewed @@ -336,7 +355,7 @@ def BaseCookieSessionFactory( exception = getattr(self.request, 'exception', None) if exception is not None: # dont set a cookie during exceptions return False - cookieval = native_(serialize( + cookieval = native_(serializer.dumps( (self.accessed, self.created, dict(self)) )) if len(cookieval) > 4064: @@ -373,7 +392,11 @@ def UnencryptedCookieSessionFactoryConfig( ): """ .. deprecated:: 1.5 - Use :func:`pyramid.session.SignedCookieSessionFactory` instead. + Use :func:`pyramid.session.SignedCookieSessionFactory` instead. + Caveat: Cookies generated using ``SignedCookieSessionFactory`` are not + compatible with cookies generated using + ``UnencryptedCookieSessionFactory``, so existing user session data + will be destroyed if you switch to it. Configure a :term:`session factory` which will provide unencrypted (but signed) cookie-based sessions. The return value of this @@ -430,9 +453,20 @@ def UnencryptedCookieSessionFactoryConfig( is valid. Default: ``signed_deserialize`` (using pickle). """ + class SerializerWrapper(object): + def __init__(self, secret): + self.secret = secret + + def loads(self, bstruct): + return signed_deserialize(bstruct, secret) + + def dumps(self, appstruct): + return signed_serialize(appstruct, secret) + + serializer = SerializerWrapper(secret) + return BaseCookieSessionFactory( - lambda v: signed_serialize(v, secret), - lambda v: signed_deserialize(v, secret), + serializer, cookie_name=cookie_name, max_age=cookie_max_age, path=cookie_path, @@ -448,6 +482,9 @@ deprecated( 'UnencryptedCookieSessionFactoryConfig', 'The UnencryptedCookieSessionFactoryConfig callable is deprecated as of ' 'Pyramid 1.5. Use ``pyramid.session.SignedCookieSessionFactory`` instead.' + ' Caveat: Cookies generated using SignedCookieSessionFactory are not ' + 'compatible with cookies generated using UnencryptedCookieSessionFactory, ' + 'so existing user session data will be destroyed if you switch to it.' ) def SignedCookieSessionFactory( @@ -463,8 +500,7 @@ def SignedCookieSessionFactory( reissue_time=0, hashalg='sha512', salt='pyramid.session.', - serialize=None, - deserialize=None, + serializer=None, ): """ .. versionadded:: 1.5 @@ -523,14 +559,17 @@ def SignedCookieSessionFactory( ``timeout`` A number of seconds of inactivity before a session times out. If - ``None`` then the cookie never expires. Default: 1200. + ``None`` then the cookie never expires. This lifetime only applies + to the *value* within the cookie. Meaning that if the cookie expires + due to a lower ``max_age``, then this setting has no effect. + Default: ``1200``. ``reissue_time`` The number of seconds that must pass before the cookie is automatically - reissued as the result of a request which accesses the session. The + reissued as the result of accessing the session. The duration is measured as the number of seconds since the last session cookie was issued and 'now'. If this value is ``0``, a new cookie - will be reissued on every request accesses the session. If ``None`` + will be reissued on every request accessing the session. If ``None`` then the cookie's lifetime will never be extended. A good rule of thumb: if you want auto-expired cookies based on @@ -546,53 +585,27 @@ def SignedCookieSessionFactory( If ``True``, set a session cookie even if an exception occurs while rendering a view. Default: ``True``. - ``serialize`` - A callable accepting a Python object and returning a bytestring. A - ``ValueError`` should be raised for malformed inputs. - Default: :func:`pickle.dumps`. - - ``deserialize`` - A callable accepting a bytestring and returning a Python object. A - ``ValueError`` should be raised for malformed inputs. - Default: :func:`pickle.loads`. + ``serializer`` + An object with two methods: ``loads`` and ``dumps``. The ``loads`` + method should accept bytes and return a Python object. The ``dumps`` + method should accept a Python object and return bytes. A ``ValueError`` + should be raised for malformed inputs. If a serializer is not passed, + the :class:`pyramid.session.PickleSerializer` serializer will be used. .. versionadded: 1.5a3 """ + if serializer is None: + serializer = PickleSerializer() - if serialize is None: - serialize = lambda v: pickle.dumps(v, pickle.HIGHEST_PROTOCOL) - - if deserialize is None: - deserialize = pickle.loads - - digestmod = lambda string=b'': hashlib.new(hashalg, string) - digest_size = digestmod().digest_size - - salted_secret = bytes_(salt or '') + bytes_(secret) - - def signed_serialize(appstruct): - cstruct = serialize(appstruct) - sig = hmac.new(salted_secret, cstruct, digestmod).digest() - return base64.b64encode(cstruct + sig) - - def signed_deserialize(bstruct): - try: - fstruct = base64.b64decode(bstruct) - except (binascii.Error, TypeError) as e: - raise ValueError('Badly formed base64 data: %s' % e) - - cstruct = fstruct[:-digest_size] - expected_sig = fstruct[-digest_size:] - - sig = hmac.new(salted_secret, cstruct, digestmod).digest() - if strings_differ(sig, expected_sig): - raise ValueError('Invalid signature') - - return deserialize(cstruct) + signed_serializer = SignedSerializer( + secret, + salt, + hashalg, + serializer=serializer, + ) return BaseCookieSessionFactory( - signed_serialize, - signed_deserialize, + signed_serializer, cookie_name=cookie_name, max_age=max_age, path=path, diff --git a/pyramid/testing.py b/pyramid/testing.py index b3460d8aa..91dc41dd5 100644 --- a/pyramid/testing.py +++ b/pyramid/testing.py @@ -320,6 +320,7 @@ class DummyRequest( method = 'GET' application_url = 'http://example.com' host = 'example.com:80' + domain = 'example.com' content_length = 0 query_string = '' charset = 'UTF-8' diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 3ac8f2d61..e25e9faa1 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -350,6 +350,14 @@ class TestRepozeWho1AuthenticationPolicy(unittest.TestCase): self.assertEqual(result[0], request.environ) self.assertEqual(result[1], {'repoze.who.userid':'fred'}) + def test_remember_kwargs(self): + authtkt = DummyWhoPlugin() + request = DummyRequest( + {'repoze.who.plugins':{'auth_tkt':authtkt}}) + policy = self._makeOne() + result = policy.remember(request, 'fred', max_age=23) + self.assertEqual(result[1], {'repoze.who.userid':'fred', 'max_age': 23}) + def test_forget_no_plugins(self): request = DummyRequest({}) policy = self._makeOne() @@ -572,7 +580,12 @@ class TestAuthTktCookieHelper(unittest.TestCase): return DummyRequest(environ, cookie=cookie) def _cookieValue(self, cookie): - return eval(cookie.value) + items = cookie.value.split('/') + D = {} + for item in items: + k, v = item.split('=', 1) + D[k] = v + return D def _parseHeaders(self, headers): return [ self._parseHeader(header) for header in headers ] @@ -838,7 +851,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): request.callbacks[0](None, response) self.assertEqual(len(response.headerlist), 3) self.assertEqual(response.headerlist[0][0], 'Set-Cookie') - self.assertTrue("'tokens': ()" in response.headerlist[0][1]) + self.assertTrue("/tokens=/" in response.headerlist[0][1]) def test_remember(self): helper = self._makeOne('secret') @@ -851,11 +864,11 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost')) + self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/')) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') - self.assertTrue(result[2][1].endswith('; Path=/; Domain=.localhost')) + self.assertTrue(result[2][1].endswith('; Domain=.localhost; Path=/')) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_include_ip(self): @@ -869,11 +882,11 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost')) + self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/')) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') - self.assertTrue(result[2][1].endswith('; Path=/; Domain=.localhost')) + self.assertTrue(result[2][1].endswith('; Domain=.localhost; Path=/')) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_path(self): @@ -889,12 +902,12 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue(result[1][1].endswith( - '; Path=/cgi-bin/app.cgi/; Domain=localhost')) + '; Domain=localhost; Path=/cgi-bin/app.cgi/')) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') self.assertTrue(result[2][1].endswith( - '; Path=/cgi-bin/app.cgi/; Domain=.localhost')) + '; Domain=.localhost; Path=/cgi-bin/app.cgi/')) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_http_only(self): @@ -922,15 +935,15 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') - self.assertTrue('; Secure' in result[0][1]) + self.assertTrue('; secure' in result[0][1]) self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.assertTrue('; Secure' in result[1][1]) + self.assertTrue('; secure' in result[1][1]) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') - self.assertTrue('; Secure' in result[2][1]) + self.assertTrue('; secure' in result[2][1]) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_wild_domain_disabled(self): @@ -944,62 +957,49 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') - self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost')) + self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/')) self.assertTrue(result[1][1].startswith('auth_tkt=')) def test_remember_parent_domain(self): helper = self._makeOne('secret', parent_domain=True) request = self._makeRequest() - request.environ['HTTP_HOST'] = 'www.example.com' + request.domain = 'www.example.com' result = helper.remember(request, 'other') self.assertEqual(len(result), 1) self.assertEqual(result[0][0], 'Set-Cookie') - self.assertTrue(result[0][1].endswith('; Path=/; Domain=.example.com')) + self.assertTrue(result[0][1].endswith('; Domain=.example.com; Path=/')) self.assertTrue(result[0][1].startswith('auth_tkt=')) def test_remember_parent_domain_supercedes_wild_domain(self): helper = self._makeOne('secret', parent_domain=True, wild_domain=True) request = self._makeRequest() - request.environ['HTTP_HOST'] = 'www.example.com' + request.domain = 'www.example.com' result = helper.remember(request, 'other') self.assertEqual(len(result), 1) - self.assertTrue(result[0][1].endswith('; Domain=.example.com')) + self.assertTrue(result[0][1].endswith('; Domain=.example.com; Path=/')) def test_remember_explicit_domain(self): helper = self._makeOne('secret', domain='pyramid.bazinga') request = self._makeRequest() - request.environ['HTTP_HOST'] = 'www.example.com' + request.domain = 'www.example.com' result = helper.remember(request, 'other') self.assertEqual(len(result), 1) self.assertEqual(result[0][0], 'Set-Cookie') - self.assertTrue(result[0][1].endswith('; Path=/; Domain=pyramid.bazinga')) + self.assertTrue(result[0][1].endswith( + '; Domain=pyramid.bazinga; Path=/')) self.assertTrue(result[0][1].startswith('auth_tkt=')) def test_remember_domain_supercedes_parent_and_wild_domain(self): helper = self._makeOne('secret', domain='pyramid.bazinga', parent_domain=True, wild_domain=True) request = self._makeRequest() - request.environ['HTTP_HOST'] = 'www.example.com' + request.domain = 'www.example.com' result = helper.remember(request, 'other') self.assertEqual(len(result), 1) - self.assertTrue(result[0][1].endswith('; Path=/; Domain=pyramid.bazinga')) - - def test_remember_domain_has_port(self): - helper = self._makeOne('secret', wild_domain=False) - request = self._makeRequest() - request.environ['HTTP_HOST'] = 'example.com:80' - result = helper.remember(request, 'other') - self.assertEqual(len(result), 2) - - self.assertEqual(result[0][0], 'Set-Cookie') - self.assertTrue(result[0][1].endswith('; Path=/')) - self.assertTrue(result[0][1].startswith('auth_tkt=')) - - self.assertEqual(result[1][0], 'Set-Cookie') - self.assertTrue(result[1][1].endswith('; Path=/; Domain=example.com')) - self.assertTrue(result[1][1].startswith('auth_tkt=')) + self.assertTrue(result[0][1].endswith( + '; Domain=pyramid.bazinga; Path=/')) def test_remember_binary_userid(self): import base64 @@ -1010,7 +1010,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) val = self._cookieValue(values[0]) self.assertEqual(val['userid'], - bytes_(base64.b64encode(b'userid').strip())) + text_(base64.b64encode(b'userid').strip())) self.assertEqual(val['user_data'], 'userid_type:b64str') def test_remember_int_userid(self): @@ -1044,7 +1044,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) val = self._cookieValue(values[0]) self.assertEqual(val['userid'], - base64.b64encode(userid.encode('utf-8'))) + text_(base64.b64encode(userid.encode('utf-8')))) self.assertEqual(val['user_data'], 'userid_type:b64unicode') def test_remember_insane_userid(self): @@ -1074,13 +1074,13 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') - self.assertTrue("'tokens': ('foo', 'bar')" in result[0][1]) + self.assertTrue("/tokens=foo|bar/" in result[0][1]) self.assertEqual(result[1][0], 'Set-Cookie') - self.assertTrue("'tokens': ('foo', 'bar')" in result[1][1]) + self.assertTrue("/tokens=foo|bar/" in result[1][1]) self.assertEqual(result[2][0], 'Set-Cookie') - self.assertTrue("'tokens': ('foo', 'bar')" in result[2][1]) + self.assertTrue("/tokens=foo|bar/" in result[2][1]) def test_remember_unicode_but_ascii_token(self): helper = self._makeOne('secret') @@ -1088,7 +1088,7 @@ class TestAuthTktCookieHelper(unittest.TestCase): la = text_(b'foo', 'utf-8') result = helper.remember(request, 'other', tokens=(la,)) # tokens must be str type on both Python 2 and 3 - self.assertTrue("'tokens': ('foo',)" in result[0][1]) + self.assertTrue("/tokens=foo/" in result[0][1]) def test_remember_nonascii_token(self): helper = self._makeOne('secret') @@ -1112,18 +1112,25 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(len(headers), 3) name, value = headers[0] self.assertEqual(name, 'Set-Cookie') - self.assertEqual(value, - 'auth_tkt=""; Path=/; Max-Age=0; Expires=Wed, 31-Dec-97 23:59:59 GMT') + self.assertEqual( + value, + 'auth_tkt=; Max-Age=0; Path=/; ' + 'expires=Wed, 31-Dec-97 23:59:59 GMT' + ) name, value = headers[1] self.assertEqual(name, 'Set-Cookie') - self.assertEqual(value, - 'auth_tkt=""; Path=/; Max-Age=0; ' - 'Expires=Wed, 31-Dec-97 23:59:59 GMT; Domain=localhost') + self.assertEqual( + value, + 'auth_tkt=; Domain=localhost; Max-Age=0; Path=/; ' + 'expires=Wed, 31-Dec-97 23:59:59 GMT' + ) name, value = headers[2] self.assertEqual(name, 'Set-Cookie') - self.assertEqual(value, - 'auth_tkt=""; Path=/; Max-Age=0; ' - 'Expires=Wed, 31-Dec-97 23:59:59 GMT; Domain=.localhost') + self.assertEqual( + value, + 'auth_tkt=; Domain=.localhost; Max-Age=0; Path=/; ' + 'expires=Wed, 31-Dec-97 23:59:59 GMT' + ) class TestAuthTicket(unittest.TestCase): def _makeOne(self, *arg, **kw): @@ -1417,7 +1424,19 @@ class TestBasicAuthAuthenticationPolicy(unittest.TestCase): self.assertEqual(policy.forget(None), [ ('WWW-Authenticate', 'Basic realm="SomeRealm"')]) +class TestSimpleSerializer(unittest.TestCase): + def _makeOne(self): + from pyramid.authentication import _SimpleSerializer + return _SimpleSerializer() + + def test_loads(self): + inst = self._makeOne() + self.assertEqual(inst.loads(b'abc'), text_('abc')) + def test_dumps(self): + inst = self._makeOne() + self.assertEqual(inst.dumps('abc'), bytes_('abc')) + class DummyContext: pass @@ -1429,6 +1448,7 @@ class DummyCookies(object): return self.cookie class DummyRequest: + domain = 'localhost' def __init__(self, environ=None, session=None, registry=None, cookie=None): self.environ = environ or {} self.session = session or {} @@ -1486,10 +1506,23 @@ class DummyAuthTktModule(object): self.kw = kw def cookie_value(self): - result = {'secret':self.secret, 'userid':self.userid, - 'remote_addr':self.remote_addr} + result = { + 'secret':self.secret, + 'userid':self.userid, + 'remote_addr':self.remote_addr + } result.update(self.kw) - result = repr(result) + tokens = result.pop('tokens', None) + if tokens is not None: + tokens = '|'.join(tokens) + result['tokens'] = tokens + items = sorted(result.items()) + new_items = [] + for k, v in items: + if isinstance(v, bytes): + v = text_(v) + new_items.append((k,v)) + result = '/'.join(['%s=%s' % (k, v) for k,v in new_items ]) return result self.AuthTicket = AuthTicket diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 051961d25..57bb5e9d0 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -3820,6 +3820,27 @@ class TestStaticURLInfo(unittest.TestCase): result = inst.generate('package:path/abc def', request, a=1) self.assertEqual(result, 'http://example.com/abc%20def') + def test_generate_url_with_custom_query(self): + inst = self._makeOne() + registrations = [('http://example.com/', 'package:path/', None)] + inst._get_registrations = lambda *x: registrations + request = self._makeRequest() + result = inst.generate('package:path/abc def', request, a=1, + _query='(openlayers)') + self.assertEqual(result, + 'http://example.com/abc%20def?(openlayers)') + + def test_generate_url_with_custom_anchor(self): + inst = self._makeOne() + registrations = [('http://example.com/', 'package:path/', None)] + inst._get_registrations = lambda *x: registrations + request = self._makeRequest() + uc = text_(b'La Pe\xc3\xb1a', 'utf-8') + result = inst.generate('package:path/abc def', request, a=1, + _anchor=uc) + self.assertEqual(result, + 'http://example.com/abc%20def#La%20Pe%C3%B1a') + def test_add_already_exists(self): inst = self._makeOne() config = self._makeConfig( diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py index 908249877..8fb766d88 100644 --- a/pyramid/tests/test_encode.py +++ b/pyramid/tests/test_encode.py @@ -72,3 +72,8 @@ class URLQuoteTests(unittest.TestCase): la = b'La/Pe\xc3\xb1a' result = self._callFUT(la, '/') self.assertEqual(result, 'La/Pe%C3%B1a') + + def test_it_with_nonstr_nonbinary(self): + la = None + result = self._callFUT(la, '/') + self.assertEqual(result, 'None') diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 9d3a9e004..35648ed38 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -640,6 +640,48 @@ class RendererScanAppTest(IntegrationBase, unittest.TestCase): res = testapp.get('/two', status=200) self.assertTrue(b'Two!' in res.body) +class UnicodeInURLTest(unittest.TestCase): + def _makeConfig(self): + from pyramid.config import Configurator + config = Configurator() + return config + + def _makeTestApp(self, config): + from webtest import TestApp + app = config.make_wsgi_app() + return TestApp(app) + + def test_unicode_in_url_404(self): + request_path = '/avalia%C3%A7%C3%A3o_participante' + request_path_unicode = b'/avalia\xc3\xa7\xc3\xa3o_participante'.decode('utf-8') + + config = self._makeConfig() + testapp = self._makeTestApp(config) + + res = testapp.get(request_path, status=404) + + # Pyramid default 404 handler outputs: + # u'404 Not Found\n\nThe resource could not be found.\n\n\n' + # u'/avalia\xe7\xe3o_participante\n\n' + self.assertTrue(request_path_unicode in res.text) + + def test_unicode_in_url_200(self): + request_path = '/avalia%C3%A7%C3%A3o_participante' + request_path_unicode = b'/avalia\xc3\xa7\xc3\xa3o_participante'.decode('utf-8') + + def myview(request): + return 'XXX' + + config = self._makeConfig() + config.add_route('myroute', request_path_unicode) + config.add_view(myview, route_name='myroute', renderer='json') + testapp = self._makeTestApp(config) + + res = testapp.get(request_path, status=200) + + self.assertEqual(res.text, '"XXX"') + + class AcceptContentTypeTest(unittest.TestCase): def setUp(self): def hello_view(request): diff --git a/pyramid/tests/test_renderers.py b/pyramid/tests/test_renderers.py index f6b9d2b0d..2bddd2318 100644 --- a/pyramid/tests/test_renderers.py +++ b/pyramid/tests/test_renderers.py @@ -567,7 +567,7 @@ class TestJSONP(unittest.TestCase): request = testing.DummyRequest() request.GET['callback'] = 'callback' result = renderer({'a':'1'}, {'request':request}) - self.assertEqual(result, 'callback({"a": "1"})') + self.assertEqual(result, 'callback({"a": "1"});') self.assertEqual(request.response.content_type, 'application/javascript') diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index b836d7d72..838e52db0 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -830,7 +830,8 @@ class TestRouter(unittest.TestCase): self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() response.app_iter = ['OK'] - view = DummyView(response, raise_exception=RuntimeError) + error = RuntimeError() + view = DummyView(response, raise_exception=error) environ = self._makeEnviron() def exception_view(context, request): self.assertEqual(request.exc_info[0], RuntimeError) @@ -842,9 +843,11 @@ class TestRouter(unittest.TestCase): start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ['OK']) - # we clean up the exc_info and exception after the request - self.assertEqual(request.exception, None) - self.assertEqual(request.exc_info, None) + # exc_info and exception should still be around on the request after + # the excview tween has run (see + # https://github.com/Pylons/pyramid/issues/1223) + self.assertEqual(request.exception, error) + self.assertEqual(request.exc_info[:2], (RuntimeError, error,)) def test_call_view_raises_exception_view(self): from pyramid.interfaces import IViewClassifier diff --git a/pyramid/tests/test_scripts/test_pshell.py b/pyramid/tests/test_scripts/test_pshell.py index 8f9f3abfb..7cb130c41 100644 --- a/pyramid/tests/test_scripts/test_pshell.py +++ b/pyramid/tests/test_scripts/test_pshell.py @@ -42,6 +42,15 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(bpython.locals_, {'foo': 'bar'}) self.assertTrue('a help message' in bpython.banner) + def test_make_ipython_v1_1_shell(self): + command = self._makeOne() + ipshell_factory = dummy.DummyIPShellFactory() + shell = command.make_ipython_v1_1_shell(ipshell_factory) + shell({'foo': 'bar'}, 'a help message') + self.assertEqual(ipshell_factory.kw['user_ns'], {'foo': 'bar'}) + self.assertTrue('a help message' in ipshell_factory.kw['banner2']) + self.assertTrue(ipshell_factory.shell.called) + def test_make_ipython_v0_11_shell(self): command = self._makeOne() ipshell_factory = dummy.DummyIPShellFactory() @@ -64,8 +73,7 @@ class TestPShellCommand(unittest.TestCase): def test_command_loads_default_shell(self): command = self._makeOne() shell = dummy.DummyShell() - command.make_ipython_v0_11_shell = lambda: None - command.make_ipython_v0_10_shell = lambda: None + command.make_ipython_shell = lambda: None command.make_bpython_shell = lambda: None command.make_default_shell = lambda: shell command.run() @@ -86,8 +94,7 @@ class TestPShellCommand(unittest.TestCase): command = self._makeOne() shell = dummy.DummyShell() bad_shell = dummy.DummyShell() - command.make_ipython_v0_11_shell = lambda: bad_shell - command.make_ipython_v0_10_shell = lambda: bad_shell + command.make_ipython_shell = lambda: bad_shell command.make_bpython_shell = lambda: bad_shell command.make_default_shell = lambda: shell command.options.python_shell = 'unknow_python_shell' @@ -106,9 +113,33 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) + def test_command_loads_ipython_v1_1(self): + command = self._makeOne() + shell = dummy.DummyShell() + command.make_ipython_v1_1_shell = lambda: shell + command.make_ipython_v0_11_shell = lambda: None + command.make_ipython_v0_10_shell = lambda: None + command.make_bpython_shell = lambda: None + command.make_default_shell = lambda: None + command.options.python_shell = 'ipython' + command.run() + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(shell.env, { + 'app':self.bootstrap.app, 'root':self.bootstrap.root, + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(shell.help) + def test_command_loads_ipython_v0_11(self): command = self._makeOne() shell = dummy.DummyShell() + command.make_ipython_v1_1_shell = lambda: None command.make_ipython_v0_11_shell = lambda: shell command.make_ipython_v0_10_shell = lambda: None command.make_bpython_shell = lambda: None @@ -131,6 +162,7 @@ class TestPShellCommand(unittest.TestCase): def test_command_loads_ipython_v0_10(self): command = self._makeOne() shell = dummy.DummyShell() + command.make_ipython_v1_1_shell = lambda: None command.make_ipython_v0_11_shell = lambda: None command.make_ipython_v0_10_shell = lambda: shell command.make_bpython_shell = lambda: None @@ -153,8 +185,7 @@ class TestPShellCommand(unittest.TestCase): def test_command_loads_bpython_shell(self): command = self._makeOne() shell = dummy.DummyBPythonShell() - command.make_ipython_v0_11_shell = lambda: None - command.make_ipython_v0_10_shell = lambda: None + command.make_ipython_shell = lambda: None command.make_bpython_shell = lambda: shell command.options.python_shell = 'bpython' command.run() @@ -173,25 +204,34 @@ class TestPShellCommand(unittest.TestCase): def test_shell_ipython_ordering(self): command = self._makeOne() + shell1_1 = dummy.DummyShell() shell0_11 = dummy.DummyShell() shell0_10 = dummy.DummyShell() + command.make_ipython_v1_1_shell = lambda: shell1_1 + shell = command.make_shell() + self.assertEqual(shell, shell1_1) + + command.make_ipython_v1_1_shell = lambda: None command.make_ipython_v0_11_shell = lambda: shell0_11 - command.make_ipython_v0_10_shell = lambda: shell0_10 - command.make_bpython_shell = lambda: None shell = command.make_shell() self.assertEqual(shell, shell0_11) + command.make_ipython_v0_11_shell = lambda: None + command.make_ipython_v0_10_shell = lambda: shell0_10 + shell = command.make_shell() + self.assertEqual(shell, shell0_10) + command.options.python_shell = 'ipython' + command.make_ipython_v1_1_shell = lambda: shell1_1 shell = command.make_shell() - self.assertEqual(shell, shell0_11) + self.assertEqual(shell, shell1_1) def test_shell_ordering(self): command = self._makeOne() ipshell = dummy.DummyShell() bpshell = dummy.DummyShell() dshell = dummy.DummyShell() - command.make_ipython_v0_11_shell = lambda: None - command.make_ipython_v0_10_shell = lambda: None + command.make_ipython_shell = lambda: None command.make_bpython_shell = lambda: None command.make_default_shell = lambda: dshell @@ -206,7 +246,7 @@ class TestPShellCommand(unittest.TestCase): shell = command.make_shell() self.assertEqual(shell, dshell) - command.make_ipython_v0_11_shell = lambda: ipshell + command.make_ipython_shell = lambda: ipshell command.make_bpython_shell = lambda: bpshell command.options.python_shell = 'ipython' shell = command.make_shell() diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index a9f70d6a0..1aaa7a2ba 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -52,6 +52,15 @@ class SharedCookieSessionTests(object): session = self._makeOne(request, timeout=1) self.assertEqual(dict(session), {}) + def test_timeout_never(self): + import time + request = testing.DummyRequest() + LONG_TIME = 31536000 + cookieval = self._serialize((time.time() + LONG_TIME, 0, {'state': 1})) + request.cookies['session'] = cookieval + session = self._makeOne(request, timeout=None) + self.assertEqual(dict(session), {'state': 1}) + def test_changed(self): request = testing.DummyRequest() session = self._makeOne(request) @@ -264,8 +273,8 @@ class SharedCookieSessionTests(object): class TestBaseCookieSession(SharedCookieSessionTests, unittest.TestCase): def _makeOne(self, request, **kw): from pyramid.session import BaseCookieSessionFactory - return BaseCookieSessionFactory( - dummy_serialize, dummy_deserialize, **kw)(request) + serializer = DummySerializer() + return BaseCookieSessionFactory(serializer, **kw)(request) def _serialize(self, value): return json.dumps(value) @@ -279,6 +288,14 @@ class TestBaseCookieSession(SharedCookieSessionTests, unittest.TestCase): self.assertEqual(session['state'], 1) self.assertFalse(session._dirty) + def test_reissue_never(self): + request = testing.DummyRequest() + cookieval = self._serialize((0, 0, {'state': 1})) + request.cookies['session'] = cookieval + session = self._makeOne(request, reissue_time=None, timeout=None) + self.assertEqual(session['state'], 1) + self.assertFalse(session._dirty) + class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase): def _makeOne(self, request, **kw): from pyramid.session import SignedCookieSessionFactory @@ -294,7 +311,7 @@ class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase): digestmod = lambda: hashlib.new(hashalg) cstruct = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) sig = hmac.new(salt + b'secret', cstruct, digestmod).digest() - return base64.b64encode(cstruct + sig) + return base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=') def test_reissue_not_triggered(self): import time @@ -305,6 +322,14 @@ class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase): self.assertEqual(session['state'], 1) self.assertFalse(session._dirty) + def test_reissue_never(self): + request = testing.DummyRequest() + cookieval = self._serialize((0, 0, {'state': 1})) + request.cookies['session'] = cookieval + session = self._makeOne(request, reissue_time=None, timeout=None) + self.assertEqual(session['state'], 1) + self.assertFalse(session._dirty) + def test_custom_salt(self): import time request = testing.DummyRequest() @@ -353,11 +378,12 @@ class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase): import hmac import time request = testing.DummyRequest() - cstruct = dummy_serialize((time.time(), 0, {'state': 1})) + serializer = DummySerializer() + cstruct = serializer.dumps((time.time(), 0, {'state': 1})) sig = hmac.new(b'pyramid.session.secret', cstruct, sha512).digest() - cookieval = base64.b64encode(cstruct + sig) + cookieval = base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=') request.cookies['session'] = cookieval - session = self._makeOne(request, deserialize=dummy_deserialize) + session = self._makeOne(request, serializer=serializer) self.assertEqual(session['state'], 1) def test_invalid_data_size(self): @@ -382,7 +408,7 @@ class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase): try: result = callbacks[0](request, response) - except TypeError as e: # pragma: no cover + except TypeError: # pragma: no cover self.fail('HMAC failed to initialize due to key length.') self.assertEqual(result, None) @@ -413,8 +439,9 @@ class TestUnencryptedCookieSession(SharedCookieSessionTests, unittest.TestCase): kw.setdefault(dest, kw.pop(src)) def _serialize(self, value): + from pyramid.compat import bytes_ from pyramid.session import signed_serialize - return signed_serialize(value, 'secret') + return bytes_(signed_serialize(value, 'secret')) def test_serialize_option(self): from pyramid.response import Response @@ -596,11 +623,12 @@ class Test_check_csrf_token(unittest.TestCase): result = self._callFUT(request, 'csrf_token', raises=False) self.assertEqual(result, False) -def dummy_serialize(value): - return json.dumps(value).encode('utf-8') +class DummySerializer(object): + def dumps(self, value): + return json.dumps(value).encode('utf-8') -def dummy_deserialize(value): - return json.loads(value.decode('utf-8')) + def loads(self, value): + return json.loads(value.decode('utf-8')) class DummySessionFactory(dict): _dirty = False diff --git a/pyramid/tests/test_url.py b/pyramid/tests/test_url.py index f6117777f..0a788ba97 100644 --- a/pyramid/tests/test_url.py +++ b/pyramid/tests/test_url.py @@ -6,7 +6,6 @@ from pyramid import testing from pyramid.compat import ( text_, - native_, WIN, ) @@ -93,6 +92,14 @@ class TestURLMethodsMixin(unittest.TestCase): result = request.resource_url(context, 'a b c') self.assertEqual(result, 'http://example.com:5432/context/a%20b%20c') + def test_resource_url_with_query_str(self): + request = self._makeOne() + self._registerResourceURL(request.registry) + context = DummyContext() + result = request.resource_url(context, 'a', query='(openlayers)') + self.assertEqual(result, + 'http://example.com:5432/context/a?(openlayers)') + def test_resource_url_with_query_dict(self): request = self._makeOne() self._registerResourceURL(request.registry) @@ -149,23 +156,18 @@ class TestURLMethodsMixin(unittest.TestCase): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() - uc = text_(b'La Pe\xc3\xb1a', 'utf-8') + uc = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.resource_url(context, anchor=uc) - self.assertEqual( - result, - native_( - text_(b'http://example.com:5432/context/#La Pe\xc3\xb1a', - 'utf-8'), - 'utf-8') - ) + self.assertEqual(result, + 'http://example.com:5432/context/#La%20Pe%C3%B1a') - def test_resource_url_anchor_is_not_urlencoded(self): + def test_resource_url_anchor_is_urlencoded_safe(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() - result = request.resource_url(context, anchor=' /#') + result = request.resource_url(context, anchor=' /#?&+') self.assertEqual(result, - 'http://example.com:5432/context/# /#') + 'http://example.com:5432/context/#%20/%23?&+') def test_resource_url_no_IResourceURL_registered(self): # falls back to ResourceURL @@ -448,14 +450,8 @@ class TestURLMethodsMixin(unittest.TestCase): request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _anchor=b"La Pe\xc3\xb1a") - self.assertEqual( - result, - native_( - text_( - b'http://example.com:5432/1/2/3#La Pe\xc3\xb1a', - 'utf-8'), - 'utf-8') - ) + self.assertEqual(result, + 'http://example.com:5432/1/2/3#La%20Pe%C3%B1a') def test_route_url_with_anchor_unicode(self): from pyramid.interfaces import IRoutesMapper @@ -465,14 +461,8 @@ class TestURLMethodsMixin(unittest.TestCase): anchor = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.route_url('flub', _anchor=anchor) - self.assertEqual( - result, - native_( - text_( - b'http://example.com:5432/1/2/3#La Pe\xc3\xb1a', - 'utf-8'), - 'utf-8') - ) + self.assertEqual(result, + 'http://example.com:5432/1/2/3#La%20Pe%C3%B1a') def test_route_url_with_query(self): from pyramid.interfaces import IRoutesMapper @@ -483,6 +473,15 @@ class TestURLMethodsMixin(unittest.TestCase): self.assertEqual(result, 'http://example.com:5432/1/2/3?q=1') + def test_route_url_with_query_str(self): + from pyramid.interfaces import IRoutesMapper + request = self._makeOne() + mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) + request.registry.registerUtility(mapper, IRoutesMapper) + result = request.route_url('flub', _query='(openlayers)') + self.assertEqual(result, + 'http://example.com:5432/1/2/3?(openlayers)') + def test_route_url_with_empty_query(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() diff --git a/pyramid/tweens.py b/pyramid/tweens.py index cf2238deb..831de8481 100644 --- a/pyramid/tweens.py +++ b/pyramid/tweens.py @@ -20,8 +20,14 @@ def excview_tween_factory(handler, registry): try: response = handler(request) except Exception as exc: - # WARNING: do not assign the result of sys.exc_info() to a - # local var here, doing so will cause a leak + # WARNING: do not assign the result of sys.exc_info() to a local + # var here, doing so will cause a leak. We used to actually + # explicitly delete both "exception" and "exc_info" from ``attrs`` + # in a ``finally:`` clause below, but now we do not because these + # attributes are useful to upstream tweens. This actually still + # apparently causes a reference cycle, but it is broken + # successfully by the garbage collector (see + # https://github.com/Pylons/pyramid/issues/1223). attrs['exc_info'] = sys.exc_info() attrs['exception'] = exc # clear old generated request.response, if any; it may @@ -38,12 +44,6 @@ def excview_tween_factory(handler, registry): if view_callable is None: raise response = view_callable(exc, request) - finally: - # prevent leakage (wrt exc_info) - if 'exc_info' in attrs: - del attrs['exc_info'] - if 'exception' in attrs: - del attrs['exception'] return response diff --git a/pyramid/url.py b/pyramid/url.py index fda2c72c7..bf4d4ff48 100644 --- a/pyramid/url.py +++ b/pyramid/url.py @@ -12,12 +12,13 @@ from pyramid.interfaces import ( ) from pyramid.compat import ( - native_, bytes_, - text_type, - url_quote, + string_types, ) -from pyramid.encode import urlencode +from pyramid.encode import ( + url_quote, + urlencode, +) from pyramid.path import caller_package from pyramid.threadlocal import get_current_registry @@ -27,6 +28,48 @@ from pyramid.traversal import ( ) PATH_SAFE = '/:@&+$,' # from webob +QUERY_SAFE = '/?:@!$&\'()*+,;=' # RFC 3986 +ANCHOR_SAFE = QUERY_SAFE + +def parse_url_overrides(kw): + """Parse special arguments passed when generating urls. + + The supplied dictionary is mutated, popping arguments as necessary. + Returns a 6-tuple of the format ``(app_url, scheme, host, port, + qs, anchor)``. + """ + anchor = '' + qs = '' + app_url = None + host = None + scheme = None + port = None + + if '_query' in kw: + query = kw.pop('_query') + if isinstance(query, string_types): + qs = '?' + url_quote(query, QUERY_SAFE) + elif query: + qs = '?' + urlencode(query, doseq=True) + + if '_anchor' in kw: + anchor = kw.pop('_anchor') + anchor = url_quote(anchor, ANCHOR_SAFE) + anchor = '#' + anchor + + if '_app_url' in kw: + app_url = kw.pop('_app_url') + + if '_host' in kw: + host = kw.pop('_host') + + if '_scheme' in kw: + scheme = kw.pop('_scheme') + + if '_port' in kw: + port = kw.pop('_port') + + return app_url, scheme, host, port, qs, anchor class URLMethodsMixin(object): """ Request methods mixin for BaseRequest having to do with URL @@ -124,18 +167,22 @@ class URLMethodsMixin(object): ``*remainder`` replacement value, it is tacked on to the URL after being URL-quoted-except-for-embedded-slashes. - If no ``_query`` keyword argument is provided, the request - query string will be returned in the URL. If it is present, it - will be used to compose a query string that will be tacked on - to the end of the URL, replacing any request query string. - The value of ``_query`` must be a sequence of two-tuples *or* - a data structure with an ``.items()`` method that returns a - sequence of two-tuples (presumably a dictionary). This data - structure will be turned into a query string per the - documentation of :func:`pyramid.encode.urlencode` function. - After the query data is turned into a query string, a leading - ``?`` is prepended, and the resulting string is appended to - the generated URL. + If no ``_query`` keyword argument is provided, the request query string + will be returned in the URL. If it is present, it will be used to + compose a query string that will be tacked on to the end of the URL, + replacing any request query string. The value of ``_query`` may be a + sequence of two-tuples *or* a data structure with an ``.items()`` + method that returns a sequence of two-tuples (presumably a dictionary). + This data structure will be turned into a query string per the + documentation of :func:`pyramid.url.urlencode` function. This will + produce a query string in the ``x-www-form-urlencoded`` format. A + non-``x-www-form-urlencoded`` query string may be used by passing a + *string* value as ``_query`` in which case it will be URL-quoted + (e.g. query="foo bar" will become "foo%20bar"). However, the result + will not need to be in ``k=v`` form as required by + ``x-www-form-urlencoded``. After the query data is turned into a query + string, a leading ``?`` is prepended, and the resulting string is + appended to the generated URL. .. note:: @@ -146,8 +193,13 @@ class URLMethodsMixin(object): as values, and a k=v pair will be placed into the query string for each value. + .. versionchanged:: 1.5 + Allow the ``_query`` option to be a string to enable alternative + encodings. + If a keyword argument ``_anchor`` is present, its string - representation will be used as a named anchor in the generated URL + representation will be quoted per :rfc:`3986#section-3.5` and used as + a named anchor in the generated URL (e.g. if ``_anchor`` is passed as ``foo`` and the route URL is ``http://example.com/route/url``, the resulting generated URL will be ``http://example.com/route/url#foo``). @@ -156,8 +208,11 @@ class URLMethodsMixin(object): If ``_anchor`` is passed as a string, it should be UTF-8 encoded. If ``_anchor`` is passed as a Unicode object, it will be converted to - UTF-8 before being appended to the URL. The anchor value is not - quoted in any way before being appended to the generated URL. + UTF-8 before being appended to the URL. + + .. versionchanged:: 1.5 + The ``_anchor`` option will be escaped instead of using + its raw string representation. If both ``_anchor`` and ``_query`` are specified, the anchor element will always follow the query element, @@ -213,34 +268,7 @@ class URLMethodsMixin(object): if route.pregenerator is not None: elements, kw = route.pregenerator(self, elements, kw) - anchor = '' - qs = '' - app_url = None - host = None - scheme = None - port = None - - if '_query' in kw: - query = kw.pop('_query') - if query: - qs = '?' + urlencode(query, doseq=True) - - if '_anchor' in kw: - anchor = kw.pop('_anchor') - anchor = native_(anchor, 'utf-8') - anchor = '#' + anchor - - if '_app_url' in kw: - app_url = kw.pop('_app_url') - - if '_host' in kw: - host = kw.pop('_host') - - if '_scheme' in kw: - scheme = kw.pop('_scheme') - - if '_port' in kw: - port = kw.pop('_port') + app_url, scheme, host, port, qs, anchor = parse_url_overrides(kw) if app_url is None: if (scheme is not None or host is not None or port is not None): @@ -331,17 +359,22 @@ class URLMethodsMixin(object): .. warning:: if no ``elements`` arguments are specified, the resource URL will end with a trailing slash. If any ``elements`` are used, the generated URL will *not* - end in trailing a slash. - - If a keyword argument ``query`` is present, it will be used to - compose a query string that will be tacked on to the end of the URL. - The value of ``query`` must be a sequence of two-tuples *or* a data - structure with an ``.items()`` method that returns a sequence of - two-tuples (presumably a dictionary). This data structure will be - turned into a query string per the documentation of - ``pyramid.url.urlencode`` function. After the query data is turned - into a query string, a leading ``?`` is prepended, and the resulting - string is appended to the generated URL. + end in a trailing slash. + + If a keyword argument ``query`` is present, it will be used to compose + a query string that will be tacked on to the end of the URL. The value + of ``query`` may be a sequence of two-tuples *or* a data structure with + an ``.items()`` method that returns a sequence of two-tuples + (presumably a dictionary). This data structure will be turned into a + query string per the documentation of :func:``pyramid.url.urlencode`` + function. This will produce a query string in the + ``x-www-form-urlencoded`` encoding. A non-``x-www-form-urlencoded`` + query string may be used by passing a *string* value as ``query`` in + which case it will be URL-quoted (e.g. query="foo bar" will become + "foo%20bar"). However, the result will not need to be in ``k=v`` form + as required by ``x-www-form-urlencoded``. After the query data is + turned into a query string, a leading ``?`` is prepended, and the + resulting string is appended to the generated URL. .. note:: @@ -352,6 +385,10 @@ class URLMethodsMixin(object): as values, and a k=v pair will be placed into the query string for each value. + .. versionchanged:: 1.5 + Allow the ``query`` option to be a string to enable alternative + encodings. + If a keyword argument ``anchor`` is present, its string representation will be used as a named anchor in the generated URL (e.g. if ``anchor`` is passed as ``foo`` and the resource URL is @@ -362,8 +399,11 @@ class URLMethodsMixin(object): If ``anchor`` is passed as a string, it should be UTF-8 encoded. If ``anchor`` is passed as a Unicode object, it will be converted to - UTF-8 before being appended to the URL. The anchor value is not - quoted in any way before being appended to the generated URL. + UTF-8 before being appended to the URL. + + .. versionchanged:: 1.5 + The ``anchor`` option will be escaped instead of using + its raw string representation. If both ``anchor`` and ``query`` are specified, the anchor element will always follow the query element, @@ -402,8 +442,11 @@ class URLMethodsMixin(object): If the ``resource`` passed in has a ``__resource_url__`` method, it will be used to generate the URL (scheme, host, port, path) for the - base resource which is operated upon by this function. See also - :ref:`overriding_resource_url_generation`. + base resource which is operated upon by this function. + + .. seealso:: + + See also :ref:`overriding_resource_url_generation`. .. versionadded:: 1.5 ``route_name``, ``route_kw``, and ``route_remainder_name`` @@ -580,13 +623,14 @@ class URLMethodsMixin(object): if 'query' in kw: query = kw['query'] - if query: + if isinstance(query, string_types): + qs = '?' + url_quote(query, QUERY_SAFE) + elif query: qs = '?' + urlencode(query, doseq=True) if 'anchor' in kw: anchor = kw['anchor'] - if isinstance(anchor, text_type): - anchor = native_(anchor, 'utf-8') + anchor = url_quote(anchor, ANCHOR_SAFE) anchor = '#' + anchor if elements: diff --git a/pyramid/util.py b/pyramid/util.py index 73f3ebdb0..6b92f17fc 100644 --- a/pyramid/util.py +++ b/pyramid/util.py @@ -26,6 +26,8 @@ class DottedNameResolver(_DottedNameResolver): def __init__(self, package=None): # default to package = None for bw compat return _DottedNameResolver.__init__(self, package) +_marker = object() + class InstancePropertyMixin(object): """ Mixin that will allow an instance to add properties at run-time as if they had been defined via @property or @reify @@ -80,6 +82,25 @@ class InstancePropertyMixin(object): if attrs: parent = self.__class__ cls = type(parent.__name__, (parent, object), attrs) + # We assign __provides__, __implemented__ and __providedBy__ below + # to prevent a memory leak that results from from the usage of this + # instance's eventual use in an adapter lookup. Adapter lookup + # results in ``zope.interface.implementedBy`` being called with the + # newly-created class as an argument. Because the newly-created + # class has no interface specification data of its own, lookup + # causes new ClassProvides and Implements instances related to our + # just-generated class to be created and set into the newly-created + # class' __dict__. We don't want these instances to be created; we + # want this new class to behave exactly like it is the parent class + # instead. See https://github.com/Pylons/pyramid/issues/1212 for + # more information. + for name in ('__implemented__', '__providedBy__', '__provides__'): + # we assign these attributes conditionally to make it possible + # to test this class in isolation without having any interfaces + # attached to it + val = getattr(parent, name, _marker) + if val is not _marker: + setattr(cls, name, val) self.__class__ = cls def _set_extensions(self, extensions): diff --git a/pyramid/view.py b/pyramid/view.py index 55ab38871..02ac8849f 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -162,12 +162,16 @@ class view_config(object): in a class or module context. It's not often used, but it can be useful in this circumstance. See the ``attach`` function in Venusian for more information. + + .. seealso:: + + See also :ref:`mapping_views_using_a_decorator_section` for + details about using :class:`pyramid.view.view_config`. - See :ref:`mapping_views_using_a_decorator_section` for details about - using :class:`pyramid.view.view_config`. - - ATTENTION: ``view_config`` will work ONLY on module top level members - because of the limitation of ``venusian.Scanner.scan``. + .. warning:: + + ``view_config`` will work ONLY on module top level members + because of the limitation of ``venusian.Scanner.scan``. """ venusian = venusian # for testing injection @@ -245,6 +249,8 @@ class AppendSlashNotFoundViewFactory(object): view callable calling convention of ``(context, request)`` (``context`` will be the exception object). + .. deprecated:: 1.3 + """ def __init__(self, notfound_view=None): if notfound_view is None: @@ -286,7 +292,7 @@ view as the Not Found view:: from pyramid.view import append_slash_notfound_view config.add_view(append_slash_notfound_view, context=HTTPNotFound) -See also :ref:`changing_the_notfound_view`. +.. deprecated:: 1.3 """ @@ -39,7 +39,7 @@ except IOError: install_requires=[ 'setuptools', - 'WebOb >= 1.2b3', # request.path_info is unicode + 'WebOb >= 1.3.1', # request.domain and CookieProfile 'repoze.lru >= 0.4', # py3 compat 'zope.interface >= 3.8.0', # has zope.interface.registry 'zope.deprecation >= 3.5.0', # py3 compat @@ -63,13 +63,12 @@ docs_extras = [ testing_extras = tests_require + [ 'nose', - 'nose-selecttests', 'coverage', 'virtualenv', # for scaffolding tests ] setup(name='pyramid', - version='1.5a2', + version='1.5b1', description='The Pyramid Web Framework, a Pylons project', long_description=README + '\n\n' + CHANGES, classifiers=[ |
