summaryrefslogtreecommitdiff
path: root/docs/tutorials
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tutorials')
-rw-r--r--docs/tutorials/wiki2/authorization.rst180
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst214
-rw-r--r--docs/tutorials/wiki2/definingmodels.rst49
-rw-r--r--docs/tutorials/wiki2/definingviews.rst125
-rw-r--r--docs/tutorials/wiki2/design.rst176
-rw-r--r--docs/tutorials/wiki2/installation.rst18
-rw-r--r--docs/tutorials/wiki2/src/authorization/development.ini4
-rw-r--r--docs/tutorials/wiki2/src/authorization/production.ini14
-rw-r--r--docs/tutorials/wiki2/src/authorization/setup.py2
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/__init__.py18
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py7
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py49
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models/mymodel.py (renamed from docs/tutorials/wiki2/src/authorization/tutorial/models.py)19
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py25
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/security/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/security/default.py (renamed from docs/tutorials/wiki2/src/authorization/tutorial/security.py)0
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css2
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.jinja2 (renamed from docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt)31
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt)18
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/login.jinja2 (renamed from docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt)22
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.jinja28
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/templates/view.jinja2 (renamed from docs/tutorials/wiki2/src/tests/tutorial/templates/view.pt)33
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/tests.py175
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views/default.py (renamed from docs/tutorials/wiki2/src/tests/tutorial/views.py)39
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/development.ini4
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/production.ini2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/setup.py2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py12
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models.py27
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py7
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py49
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py17
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py30
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css2
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt)18
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja28
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py68
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py (renamed from docs/tutorials/wiki2/src/basiclayout/tutorial/views.py)18
-rw-r--r--docs/tutorials/wiki2/src/models/development.ini4
-rw-r--r--docs/tutorials/wiki2/src/models/production.ini14
-rw-r--r--docs/tutorials/wiki2/src/models/setup.py2
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/__init__.py12
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models.py25
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/__init__.py7
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/meta.py49
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py14
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py25
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css2
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt)18
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja28
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/tests.py68
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/views/default.py (renamed from docs/tutorials/wiki2/src/models/tutorial/views.py)18
-rw-r--r--docs/tutorials/wiki2/src/tests/development.ini4
-rw-r--r--docs/tutorials/wiki2/src/tests/production.ini14
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py4
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/__init__.py18
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py7
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/meta.py49
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/mymodel.py (renamed from docs/tutorials/wiki2/src/tests/tutorial/models.py)19
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py25
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/security/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/security/default.py (renamed from docs/tutorials/wiki2/src/tests/tutorial/security.py)0
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css2
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2 (renamed from docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt)27
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 (renamed from docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt)18
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja274
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt54
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.jinja28
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2 (renamed from docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt)29
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests.py235
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/__init__.py1
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py141
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py168
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/default.py (renamed from docs/tutorials/wiki2/src/authorization/tutorial/views.py)40
-rw-r--r--docs/tutorials/wiki2/src/views/development.ini4
-rw-r--r--docs/tutorials/wiki2/src/views/production.ini14
-rw-r--r--docs/tutorials/wiki2/src/views/setup.py2
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/__init__.py12
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models.py25
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/__init__.py7
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/meta.py49
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models/mymodel.py14
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py25
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/edit.jinja2 (renamed from docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt)21
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja266
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.jinja28
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt66
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/templates/view.jinja2 (renamed from docs/tutorials/wiki2/src/views/tutorial/templates/view.pt)23
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/tests.py175
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views/default.py (renamed from docs/tutorials/wiki2/src/views/tutorial/views.py)25
-rw-r--r--docs/tutorials/wiki2/tests.rst75
96 files changed, 1814 insertions, 1494 deletions
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index 1d810b05b..e40433497 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -18,22 +18,22 @@ require permission, instead of a default "403 Forbidden" page.
We will implement the access control with the following steps:
-* Add users and groups (``security.py``, a new module).
-* Add an :term:`ACL` (``models.py`` and ``__init__.py``).
+* Add users and groups (``security/default.py``, a new subpackage).
+* Add an :term:`ACL` (``models/mymodel.py`` and ``__init__.py``).
* Add an :term:`authentication policy` and an :term:`authorization policy`
(``__init__.py``).
* Add :term:`permission` declarations to the ``edit_page`` and ``add_page``
- views (``views.py``).
+ views (``views/default.py``).
Then we will add the login and logout feature:
* Add routes for /login and /logout (``__init__.py``).
-* Add ``login`` and ``logout`` views (``views.py``).
-* Add a login template (``login.pt``).
+* Add ``login`` and ``logout`` views (``views/default.py``).
+* Add a login template (``login.jinja2``).
* Make the existing views return a ``logged_in`` flag to the renderer
- (``views.py``).
+ (``views/default.py``).
* Add a "Logout" link to be shown when logged in and viewing or editing a page
- (``view.pt``, ``edit.pt``).
+ (``view.jinja2``, ``edit.jinja2``).
Access control
@@ -42,10 +42,10 @@ Access control
Add users and groups
~~~~~~~~~~~~~~~~~~~~
-Create a new ``tutorial/tutorial/security.py`` module with the
+Create a new ``tutorial/tutorial/security/default.py`` subpackage with the
following content:
-.. literalinclude:: src/authorization/tutorial/security.py
+.. literalinclude:: src/authorization/tutorial/security/default.py
:linenos:
:language: python
@@ -68,20 +68,21 @@ database, but here we use "dummy" data to represent user and groups sources.
Add an ACL
~~~~~~~~~~
-Open ``tutorial/tutorial/models.py`` and add the following import
-statement at the head:
+Open ``tutorial/tutorial/models/mymodel.py`` and add the following import
+statement just after the ``Base`` import at the top:
-.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 1-4
+.. literalinclude:: src/authorization/tutorial/models/mymodel.py
+ :lines: 3-6
:linenos:
+ :lineno-start: 3
:language: python
Add the following class definition at the end:
-.. literalinclude:: src/authorization/tutorial/models.py
- :lines: 33-37
+.. literalinclude:: src/authorization/tutorial/models/mymodel.py
+ :lines: 22-26
:linenos:
- :lineno-start: 33
+ :lineno-start: 22
:language: python
We import :data:`~pyramid.security.Allow`, an action that means that
@@ -90,9 +91,9 @@ permission is allowed, and :data:`~pyramid.security.Everyone`, a special
:term:`ACE` entries that make up the ACL.
The ACL is a list that needs to be named `__acl__` and be an attribute of a
-class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry
-allows any user the `view` permission. The second entry allows the
-``group:editors`` principal the `edit` permission.
+class. We define an :term:`ACL` with two :term:`ACE` entries. The first entry
+allows any user (``Everyone``) the `view` permission. The second entry allows
+the ``group:editors`` principal the `edit` permission.
The ``RootFactory`` class that contains the ACL is a :term:`root factory`. We
need to associate it to our :app:`Pyramid` application, so the ACL is provided
@@ -104,10 +105,8 @@ our :term:`Configurator` constructor, that points to the class we created
above:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 24-25
- :linenos:
+ :lines: 13-14
:emphasize-lines: 2
- :lineno-start: 16
:language: python
Only the highlighted line needs to be added.
@@ -127,18 +126,18 @@ Open ``tutorial/tutorial/__init__.py`` and add the highlighted import
statements:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 1-7
+ :lines: 1-5
:linenos:
- :emphasize-lines: 2-3,7
+ :emphasize-lines: 2-5
:language: python
Now add those policies to the configuration:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 21-27
+ :lines: 7-16
:linenos:
- :lineno-start: 21
- :emphasize-lines: 1-3,6-7
+ :lineno-start: 7
+ :emphasize-lines: 4-6,9-10
:language: python
Only the highlighted lines need to be added.
@@ -151,47 +150,50 @@ ticket that may be included in the request. We are also enabling an
Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy`
constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is
a string representing an encryption key used by the "authentication ticket"
-machinery represented by this policy: it is required. The ``callback`` is the
+machinery represented by this policy; it is required. The ``callback`` is the
``groupfinder()`` function that we created before.
+
Add permission declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/tutorial/views.py`` and add a ``permission='edit'`` parameter
-to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 60-61
+Open ``tutorial/tutorial/views/default.py`` and add a ``permission='view'``
+parameter to the ``@view_config`` decorator for ``view_wiki()`` and
+``view_page()`` as follows:
+
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 27-29
:emphasize-lines: 1-2
:language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 75-76
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 33-35
:emphasize-lines: 1-2
:language: python
Only the highlighted lines, along with their preceding commas, need to be
edited and added.
-The result is that only users who possess the ``edit`` permission at the time
-of the request may invoke those two views.
+This allows anyone to invoke these two views.
-Add a ``permission='view'`` parameter to the ``@view_config`` decorator for
-``view_wiki()`` and ``view_page()`` as follows:
+Add a ``permission='edit'`` parameter to the ``@view_config`` decorators for
+``add_page()`` and ``edit_page()``:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 30-31
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 57-59
:emphasize-lines: 1-2
:language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 36-37
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 72-74
:emphasize-lines: 1-2
:language: python
Only the highlighted lines, along with their preceding commas, need to be
edited and added.
-This allows anyone to invoke these two views.
+The result is that only users who possess the ``edit`` permission at the time
+of the request may invoke those two views.
We are done with the changes needed to control access. The changes that
follow will add the login and logout feature.
@@ -205,7 +207,7 @@ Go back to ``tutorial/tutorial/__init__.py`` and add these two routes as
highlighted:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 30-33
+ :lines: 20-23
:emphasize-lines: 2-3
:language: python
@@ -213,7 +215,7 @@ highlighted:
``view_page`` route definition:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 33
+ :lines: 23
:language: python
This is because ``view_page``'s route definition uses a catch-all
@@ -233,11 +235,11 @@ We'll also add a ``logout`` view callable to our application and provide a
link to it. This view will clear the credentials of the logged in user and
redirect back to the front page.
-Add the following import statements to the head of
-``tutorial/tutorial/views.py``:
+Add the following import statements to ``tutorial/tutorial/views/default.py``
+after the import from ``pyramid.httpexceptions``:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 9-19
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 10-20
:emphasize-lines: 1-11
:language: python
@@ -250,18 +252,18 @@ cookie.
Now add the ``login`` and ``logout`` views at the end of the file:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 91-123
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 88-121
:language: python
``login()`` has two decorators:
- a ``@view_config`` decorator which associates it with the ``login`` route
- and makes it visible when we visit ``/login``,
+ and makes it visible when we visit ``/login``, and
- a ``@forbidden_view_config`` decorator which turns it into a
:term:`forbidden view`. ``login()`` will be invoked when a user tries to
execute a view callable for which they lack authorization. For example, if
- a user has not logged in and tries to add or edit a Wiki page, they will be
+ a user has not logged in and tries to add or edit a wiki page, they will be
shown the login form before being allowed to continue.
The order of these two :term:`view configuration` decorators is unimportant.
@@ -269,36 +271,36 @@ The order of these two :term:`view configuration` decorators is unimportant.
``logout()`` is decorated with a ``@view_config`` decorator which associates
it with the ``logout`` route. It will be invoked when we visit ``/logout``.
-Add the ``login.pt`` Template
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Add the ``login.jinja2`` template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Create ``tutorial/tutorial/templates/login.pt`` with the following content:
+Create ``tutorial/tutorial/templates/login.jinja2`` with the following content:
-.. literalinclude:: src/authorization/tutorial/templates/login.pt
+.. literalinclude:: src/authorization/tutorial/templates/login.jinja2
:language: html
The above template is referenced in the login view that we just added in
-``views.py``.
+``views/default.py``.
Return a ``logged_in`` flag to the renderer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/tutorial/views.py`` again. Add a ``logged_in`` parameter to
-the return value of ``view_page()``, ``edit_page()``, and ``add_page()`` as
-follows:
+Open ``tutorial/tutorial/views/default.py`` again. Add a ``logged_in``
+parameter to the return value of ``view_page()``, ``add_page()``, and
+``edit_page()`` as follows:
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 57-58
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 54-55
:emphasize-lines: 1-2
:language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 72-73
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 69-70
:emphasize-lines: 1-2
:language: python
-.. literalinclude:: src/authorization/tutorial/views.py
- :lines: 85-89
+.. literalinclude:: src/authorization/tutorial/views/default.py
+ :lines: 82-86
:emphasize-lines: 3-4
:language: python
@@ -310,19 +312,19 @@ the user is not authenticated, or a userid if the user is authenticated.
Add a "Logout" link when logged in
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Open ``tutorial/tutorial/templates/edit.pt`` and
-``tutorial/tutorial/templates/view.pt`` and add the following code as
+Open ``tutorial/tutorial/templates/edit.jinja2`` and
+``tutorial/tutorial/templates/view.jinja2`` and add the following code as
indicated by the highlighted lines.
-.. literalinclude:: src/authorization/tutorial/templates/edit.pt
- :lines: 34-38
- :emphasize-lines: 3-5
+.. literalinclude:: src/authorization/tutorial/templates/edit.jinja2
+ :lines: 34-40
+ :emphasize-lines: 3-7
:language: html
-The attribute ``tal:condition="logged_in"`` will make the element be included
-when ``logged_in`` is any user id. The link will invoke the logout view. The
-above element will not be included if ``logged_in`` is ``None``, such as when
-a user is not authenticated.
+The attribute ``logged_in`` will make the element be included when
+``logged_in`` is any user id. The link will invoke the logout view. The above
+element will not be included if ``logged_in`` is ``None``, such as when a user
+is not authenticated.
Reviewing our changes
---------------------
@@ -331,45 +333,45 @@ Our ``tutorial/tutorial/__init__.py`` will look like this when we're done:
.. literalinclude:: src/authorization/tutorial/__init__.py
:linenos:
- :emphasize-lines: 2-3,7,21-23,25-27,31-32
+ :emphasize-lines: 2-3,5,10-12,14-16,21-22
:language: python
Only the highlighted lines need to be added or edited.
-Our ``tutorial/tutorial/models.py`` will look like this when we're done:
+Our ``tutorial/tutorial/models/mymodel.py`` will look like this when we're done:
-.. literalinclude:: src/authorization/tutorial/models.py
+.. literalinclude:: src/authorization/tutorial/models/mymodel.py
:linenos:
- :emphasize-lines: 1-4,33-37
+ :emphasize-lines: 3-6,22-26
:language: python
Only the highlighted lines need to be added or edited.
-Our ``tutorial/tutorial/views.py`` will look like this when we're done:
+Our ``tutorial/tutorial/views/default.py`` will look like this when we're done:
-.. literalinclude:: src/authorization/tutorial/views.py
+.. literalinclude:: src/authorization/tutorial/views/default.py
:linenos:
- :emphasize-lines: 9-11,14-19,25,31,37,58,61,73,76,88,91-117,119-123
+ :emphasize-lines: 10-20,27-28,33-34,54-55,57-58,69-70,72-73,84-85,88-121
:language: python
Only the highlighted lines need to be added or edited.
-Our ``tutorial/tutorial/templates/edit.pt`` template will look like this when
+Our ``tutorial/tutorial/templates/edit.jinja2`` template will look like this when
we're done:
-.. literalinclude:: src/authorization/tutorial/templates/edit.pt
+.. literalinclude:: src/authorization/tutorial/templates/edit.jinja2
:linenos:
- :emphasize-lines: 36-38
+ :emphasize-lines: 36-40
:language: html
Only the highlighted lines need to be added or edited.
-Our ``tutorial/tutorial/templates/view.pt`` template will look like this when
+Our ``tutorial/tutorial/templates/view.jinja2`` template will look like this when
we're done:
-.. literalinclude:: src/authorization/tutorial/templates/view.pt
+.. literalinclude:: src/authorization/tutorial/templates/view.jinja2
:linenos:
- :emphasize-lines: 36-38
+ :emphasize-lines: 36-40
:language: html
Only the highlighted lines need to be added or edited.
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 695d7f15b..e3d0a0a3c 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -12,12 +12,12 @@ Application configuration with ``__init__.py``
A directory on disk can be turned into a Python :term:`package` by containing
an ``__init__.py`` file. Even if empty, this marks a directory as a Python
-package. We use ``__init__.py`` both as a marker, indicating the directory
-in which it's contained is a package, and to contain application configuration
+package. We use ``__init__.py`` both as a marker, indicating the directory in
+which it's contained is a package, and to contain application configuration
code.
-Open ``tutorial/tutorial/__init__.py``. It should already contain
-the following:
+Open ``tutorial/tutorial/__init__.py``. It should already contain the
+following:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:linenos:
@@ -36,6 +36,7 @@ the ``main`` function we've defined in our ``__init__.py``:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
:pyobject: main
+ :lineno-start: 4
:linenos:
:language: py
@@ -43,58 +44,41 @@ When you invoke the ``pserve development.ini`` command, the ``main`` function
above is executed. It accepts some settings and returns a :term:`WSGI`
application. (See :ref:`startup_chapter` for more about ``pserve``.)
-The main function first creates a :term:`SQLAlchemy` database engine using
-:func:`sqlalchemy.engine_from_config` from the ``sqlalchemy.`` prefixed
-settings in the ``development.ini`` file's ``[app:main]`` section.
-This will be a URI (something like ``sqlite://``):
-
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 13
- :language: py
-
-``main`` then initializes our SQLAlchemy session object, passing it the
-engine:
+Next in ``main``, construct a :term:`Configurator` object:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 14
- :language: py
-
-``main`` subsequently initializes our SQLAlchemy declarative ``Base`` object,
-assigning the engine we created to the ``bind`` attribute of it's
-``metadata`` object. This allows table definitions done imperatively
-(instead of declaratively, via a class statement) to work. We won't use any
-such tables in our application, but if you add one later, long after you've
-forgotten about this tutorial, you won't be left scratching your head when it
-doesn't work.
-
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 15
- :language: py
-
-The next step of ``main`` is to construct a :term:`Configurator` object:
-
- .. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 16
+ :lines: 7
+ :lineno-start: 7
:language: py
``settings`` is passed to the Configurator as a keyword argument with the
dictionary values passed as the ``**settings`` argument. This will be a
dictionary of settings parsed from the ``.ini`` file, which contains
deployment-related values such as ``pyramid.reload_templates``,
-``db_string``, etc.
+``sqlalchemy.url``, and so on.
+
+Next include :term:`Jinja2` templating bindings so that we can use renderers
+with the ``.jinja2`` extension within our project.
+
+ .. literalinclude:: src/basiclayout/tutorial/__init__.py
+ :lines: 8
+ :lineno-start: 8
+ :language: py
-Next, include :term:`Chameleon` templating bindings so that we can use
-renderers with the ``.pt`` extension within our project.
+Next include the module ``meta`` from the package ``models`` using a dotted
+Python path.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 17
+ :lines: 9
+ :lineno-start: 9
:language: py
``main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with
two arguments: ``static`` (the name), and ``static`` (the path):
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 18
+ :lines: 10
+ :lineno-start: 10
:language: py
This registers a static resource view which will match any URL that starts
@@ -112,11 +96,12 @@ via the :meth:`pyramid.config.Configurator.add_route` method that will be
used when the URL is ``/``:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 19
+ :lines: 11
+ :lineno-start: 11
:language: py
-Since this route has a ``pattern`` equaling ``/`` it is the route that will
-be matched when the URL ``/`` is visited, e.g. ``http://localhost:6543/``.
+Since this route has a ``pattern`` equaling ``/``, it is the route that will
+be matched when the URL ``/`` is visited, e.g., ``http://localhost:6543/``.
``main`` next calls the ``scan`` method of the configurator
(:meth:`pyramid.config.Configurator.scan`), which will recursively scan our
@@ -126,28 +111,32 @@ view configuration will be registered, which will allow one of our
application URLs to be mapped to some code.
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 20
+ :lines: 12
+ :lineno-start: 12
:language: py
-Finally, ``main`` is finished configuring things, so it uses the
+Finally ``main`` is finished configuring things, so it uses the
:meth:`pyramid.config.Configurator.make_wsgi_app` method to return a
:term:`WSGI` application:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 21
+ :lines: 13
+ :lineno-start: 13
:language: py
-View declarations via ``views.py``
-----------------------------------
+
+View declarations via the ``views`` package
+-------------------------------------------
The main function of a web framework is mapping each URL pattern to code (a
:term:`view callable`) that is executed when the requested URL matches the
corresponding :term:`route`. Our application uses the
:meth:`pyramid.view.view_config` decorator to perform this mapping.
-Open ``tutorial/tutorial/views.py``. It should already contain the following:
+Open ``tutorial/tutorial/views/default.py`` in the ``views`` package. It
+should already contain the following:
- .. literalinclude:: src/basiclayout/tutorial/views.py
+ .. literalinclude:: src/basiclayout/tutorial/views/default.py
:linenos:
:language: py
@@ -156,13 +145,13 @@ function it decorates (``my_view``) with a :term:`view configuration`,
consisting of:
* a ``route_name`` (``home``)
- * a ``renderer``, which is a template from the ``templates`` subdirectory
- of the package.
+ * a ``renderer``, which is a template from the ``templates`` subdirectory of
+ the package.
When the pattern associated with the ``home`` view is matched during a request,
-``my_view()`` will be executed. ``my_view()`` returns a dictionary; the
-renderer will use the ``templates/mytemplate.pt`` template to create a response
-based on the values in the dictionary.
+``my_view()`` will be executed. ``my_view()`` returns a dictionary; the
+renderer will use the ``templates/mytemplate.jinja2`` template to create a
+response based on the values in the dictionary.
Note that ``my_view()`` accepts a single argument named ``request``. This is
the standard call signature for a Pyramid :term:`view callable`.
@@ -175,67 +164,110 @@ application. Without being processed by ``scan``, the decorator effectively
does nothing. ``@view_config`` is inert without being detected via a
:term:`scan`.
-The sample ``my_view()`` created by the scaffold uses a ``try:`` and ``except:``
-clause to detect if there is a problem accessing the project database and
-provide an alternate error response. That response will include the text
-shown at the end of the file, which will be displayed in the browser to
-inform the user about possible actions to take to solve the problem.
+The sample ``my_view()`` created by the scaffold uses a ``try:`` and
+``except:`` clause to detect if there is a problem accessing the project
+database and provide an alternate error response. That response will include
+the text shown at the end of the file, which will be displayed in the browser
+to inform the user about possible actions to take to solve the problem.
+
-Content Models with ``models.py``
----------------------------------
+Content models with the ``models`` package
+------------------------------------------
In a SQLAlchemy-based application, a *model* object is an object composed by
-querying the SQL database. The ``models.py`` file is where the ``alchemy``
+querying the SQL database. The ``models`` package is where the ``alchemy``
scaffold put the classes that implement our models.
-Open ``tutorial/tutorial/models.py``. It should already contain the following:
+First, open ``tutorial/tutorial/models/__init__.py``, which should already
+contain the following:
- .. literalinclude:: src/basiclayout/tutorial/models.py
+ .. literalinclude:: src/basiclayout/tutorial/models/__init__.py
:linenos:
:language: py
-Let's examine this in detail. First, we need some imports to support later code:
+Our ``__init__.py`` will perform some imports to support later code, then calls
+the function :func:`sqlalchemy.orm.configure_mappers`.
+
+Next open ``tutorial/tutorial/models/meta.py``, which should already contain
+the following:
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :end-before: DBSession
+ .. literalinclude:: src/basiclayout/tutorial/models/meta.py
:linenos:
:language: py
-Next we set up a SQLAlchemy ``DBSession`` object:
+``meta.py`` contains imports that are used to support later code. We create a
+dictionary ``NAMING_CONVENTION`` as well.
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :lines: 17
+ .. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :end-before: metadata
+ :linenos:
+ :language: py
+
+Next we create a ``metadata`` object from the class
+:class:`sqlalchemy.schema.MetaData`, using ``NAMING_CONVENTION`` as the value
+for the ``naming_convention`` argument. We also need to create a declarative
+``Base`` object to use as a base class for our model. Then our model classes
+will inherit from the ``Base`` class so they can be associated with our
+particular database connection.
+
+ .. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :lines: 18-19
+ :lineno-start: 18
+ :linenos:
:language: py
-``scoped_session`` and ``sessionmaker`` are standard SQLAlchemy helpers.
-``scoped_session`` allows us to access our database connection globally.
-``sessionmaker`` creates a database session object. We pass to
-``sessionmaker`` the ``extension=ZopeTransactionExtension()`` extension
-option in order to allow the system to automatically manage database
-transactions. With ``ZopeTransactionExtension`` activated, our application
-will automatically issue a transaction commit after every request unless an
-exception is raised, in which case the transaction will be aborted.
+Next we define several functions, the first of which is ``includeme``, which
+configures various database settings by calling subsequently defined functions.
+
+ .. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :pyobject: includeme
+ :lineno-start: 22
+ :linenos:
+ :language: py
+
+The function ``get_session`` registers a database session with a transaction
+manager, and returns a ``dbsession`` object. With the transaction manager, our
+application will automatically issue a transaction commit after every request
+unless an exception is raised, in which case the transaction will be aborted.
+
+ .. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :pyobject: get_session
+ :lineno-start: 35
+ :linenos:
+ :language: py
-We also need to create a declarative ``Base`` object to use as a
-base class for our model:
+The ``get_engine`` function creates an :term:`SQLAlchemy` database engine using
+:func:`sqlalchemy.engine_from_config` from the ``sqlalchemy.``-prefixed
+settings in the ``development.ini`` file's ``[app:main]`` section, which is a
+URI, something like ``sqlite://``.
- .. literalinclude:: src/basiclayout/tutorial/models.py
- :lines: 18
+ .. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :pyobject: get_engine
+ :lineno-start: 42
+ :linenos:
:language: py
-Our model classes will inherit from this ``Base`` class so they can be
-associated with our particular database connection.
+The function ``get_dbmaker`` accepts an :term:`SQLAlchemy` database engine,
+and creates a database session object ``dbmaker`` from the :term:`SQLAlchemy`
+class :class:`sqlalchemy.orm.session.sessionmaker`, which is then used for
+creating a session with the database engine.
+
+ .. literalinclude:: src/basiclayout/tutorial/models/meta.py
+ :pyobject: get_dbmaker
+ :lineno-start: 46
+ :linenos:
+ :language: py
-To give a simple example of a model class, we define one named ``MyModel``:
+To give a simple example of a model class, we define one named ``MyModel``:
- .. literalinclude:: src/basiclayout/tutorial/models.py
+ .. literalinclude:: src/basiclayout/tutorial/models/mymodel.py
:pyobject: MyModel
:linenos:
:language: py
Our example model does not require an ``__init__`` method because SQLAlchemy
-supplies for us a default constructor if one is not already present,
-which accepts keyword arguments of the same name as that of the mapped attributes.
+supplies for us a default constructor if one is not already present, which
+accepts keyword arguments of the same name as that of the mapped attributes.
.. note:: Example usage of MyModel:
@@ -247,8 +279,8 @@ The ``MyModel`` class has a ``__tablename__`` attribute. This informs
SQLAlchemy which table to use to store the data representing instances of this
class.
-The Index import and the Index object creation is not required for this
-tutorial, and will be removed in the next step.
-
That's about all there is to it regarding models, views, and initialization
code in our stock application.
+
+The Index import and the Index object creation is not required for this
+tutorial, and will be removed in the next step.
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst
index b2d9bf83a..b38177d04 100644
--- a/docs/tutorials/wiki2/definingmodels.rst
+++ b/docs/tutorials/wiki2/definingmodels.rst
@@ -4,27 +4,27 @@ Defining the Domain Model
The first change we'll make to our stock ``pcreate``-generated application will
be to define a :term:`domain model` constructor representing a wiki page.
-We'll do this inside our ``models.py`` file.
+We'll do this inside our ``mymodel.py`` file.
-Edit ``models.py``
-------------------
+Edit ``mymodel.py``
+-------------------
.. note::
- There is nothing special about the filename ``models.py``. A
+ There is nothing special about the filename ``mymodel.py``. A
project may have many models throughout its codebase in arbitrarily named
files. Files implementing models often have ``model`` in their filenames
or they may live in a Python subpackage of your application package named
- ``models``, but this is only by convention.
+ ``models`` (as we've done in this tutorial), but this is only by convention.
-Open ``tutorial/tutorial/models.py`` file and edit it to look like the
-following:
+Open the ``tutorial/tutorial/models/mymodel.py`` file and edit it to look like
+the following:
-.. literalinclude:: src/models/tutorial/models.py
+.. literalinclude:: src/models/tutorial/models/mymodel.py
:linenos:
:language: py
- :emphasize-lines: 20-22,24,25
+ :emphasize-lines: 9-11,13,14
The highlighted lines are the ones that need to be changed, as well as
removing lines that reference ``Index``.
@@ -33,11 +33,11 @@ The first thing we've done is remove the stock ``MyModel`` class
from the generated ``models.py`` file. The ``MyModel`` class is only a
sample and we're not going to use it.
-Then, we added a ``Page`` class. Because this is a SQLAlchemy application,
+Then we added a ``Page`` class. Because this is an SQLAlchemy application,
this class inherits from an instance of
:func:`sqlalchemy.ext.declarative.declarative_base`.
-.. literalinclude:: src/models/tutorial/models.py
+.. literalinclude:: src/models/tutorial/models/mymodel.py
:pyobject: Page
:linenos:
:language: python
@@ -45,15 +45,33 @@ this class inherits from an instance of
As you can see, our ``Page`` class has a class level attribute
``__tablename__`` which equals the string ``'pages'``. This means that
SQLAlchemy will store our wiki data in a SQL table named ``pages``. Our
-``Page`` class will also have class-level attributes named ``id``, ``name``
+``Page`` class will also have class-level attributes named ``id``, ``name``,
and ``data`` (all instances of :class:`sqlalchemy.schema.Column`). These will
map to columns in the ``pages`` table. The ``id`` attribute will be the
primary key in the table. The ``name`` attribute will be a text attribute,
each value of which needs to be unique within the column. The ``data``
attribute is a text attribute that will hold the body of each page.
-Changing ``scripts/initializedb.py``
-------------------------------------
+
+Edit ``models/__init__.py``
+---------------------------
+
+Since we are using a package for our models, we also need to update our
+``__init__.py`` file.
+
+Open the ``tutorial/tutorial/models/__init__.py`` file and edit it to look like
+the following:
+
+.. literalinclude:: src/models/tutorial/models/__init__.py
+ :linenos:
+ :language: py
+ :emphasize-lines: 4
+
+Here we need to align our import with the name of the model ``Page``.
+
+
+Edit ``scripts/initializedb.py``
+--------------------------------
We haven't looked at the details of this file yet, but within the ``scripts``
directory of your ``tutorial`` package is a file named ``initializedb.py``.
@@ -72,12 +90,13 @@ the following:
.. literalinclude:: src/models/tutorial/scripts/initializedb.py
:linenos:
:language: python
- :emphasize-lines: 14,31,36
+ :emphasize-lines: 16,31,41
Only the highlighted lines need to be changed, as well as removing the lines
referencing ``pyramid.scripts.common`` and ``options`` under the ``main``
function.
+
Installing the project and re-initializing the database
-------------------------------------------------------
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index 0b495445a..08fa8f16b 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -14,7 +14,7 @@ and a user visits ``http://example.com/foo/bar``, our pattern would be matched
against ``/foo/bar`` and the ``matchdict`` would look like ``{'one':'foo',
'two':'bar'}``.
-Declaring Dependencies in Our ``setup.py`` File
+Declaring dependencies in our ``setup.py`` file
===============================================
The view code in our application will depend on a package which is not a
@@ -47,7 +47,7 @@ directory in which ``setup.py`` lives) and execute the following command.
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ cd tutorial
$ $VENV/bin/python setup.py develop
@@ -60,31 +60,32 @@ On Windows:
c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
Success executing this command will end with a line to the console something
-like::
+like this::
Finished processing dependencies for tutorial==0.0
-Adding view functions in ``views.py``
-=====================================
+Adding view functions in ``views/default.py``
+=============================================
-It's time for a major change. Open ``tutorial/tutorial/views.py`` and edit it
-to look like the following:
+It's time for a major change. Open ``tutorial/tutorial/views/default.py`` and
+edit it to look like the following:
-.. literalinclude:: src/views/tutorial/views.py
+.. literalinclude:: src/views/tutorial/views/default.py
:linenos:
:language: python
- :emphasize-lines: 1-7,14,16-72
+ :emphasize-lines: 1-9,12-70
The highlighted lines need to be added or edited.
-We added some imports and created a regular expression to find "WikiWords".
+We added some imports, and created a regular expression to find "WikiWords".
We got rid of the ``my_view`` view function and its decorator that was added
when we originally rendered the ``alchemy`` scaffold. It was only an example
-and isn't relevant to our application.
+and isn't relevant to our application. We also delated the ``db_err_msg``
+string.
-Then we added four :term:`view callable` functions to our ``views.py``
-module:
+Then we added four :term:`view callable` functions to our ``views/default.py``
+module:
* ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL.
* ``view_page()`` - Displays an individual page.
@@ -95,20 +96,20 @@ We'll describe each one briefly in the following sections.
.. note::
- There is nothing special about the filename ``views.py``. A project may
- have many view callables throughout its codebase in arbitrarily named
- files. Files implementing view callables often have ``view`` in their
- filenames (or may live in a Python subpackage of your application package
- named ``views``), but this is only by convention.
+ There is nothing special about the filename ``default.py``. A project may
+ have many view callables throughout its codebase in arbitrarily named files.
+ Files implementing view callables often have ``view`` in their filenames (or
+ may live in a Python subpackage of your application package named ``views``,
+ as in our case), but this is only by convention.
The ``view_wiki`` view function
-------------------------------
Following is the code for the ``view_wiki`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 20-24
- :lineno-start: 20
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 17-20
+ :lineno-start: 17
:linenos:
:language: python
@@ -130,9 +131,9 @@ The ``view_page`` view function
Here is the code for the ``view_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 25-45
- :lineno-start: 25
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 22-42
+ :lineno-start: 22
:linenos:
:language: python
@@ -158,17 +159,17 @@ template, and we return a dictionary with a number of arguments. The fact that
``view_page()`` returns a dictionary (as opposed to a :term:`response` object)
is a cue to :app:`Pyramid` that it should try to use a :term:`renderer`
associated with the view configuration to render a response. In our case, the
-renderer used will be the ``templates/view.pt`` template, as indicated in the
-``@view_config`` decorator that is applied to ``view_page()``.
+renderer used will be the ``templates/view.jinja2`` template, as indicated in
+the ``@view_config`` decorator that is applied to ``view_page()``.
The ``add_page`` view function
------------------------------
Here is the code for the ``add_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 47-58
- :lineno-start: 47
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 44-55
+ :lineno-start: 44
:linenos:
:language: python
@@ -189,15 +190,15 @@ If the view execution *is* a result of a form submission (i.e., the expression
``'form.submitted' in request.params`` is ``True``), we grab the page body
from the form data, create a Page object with this page body and the name
taken from ``matchdict['pagename']``, and save it into the database using
-``DBSession.add``. We then redirect back to the ``view_page`` view for the
-newly created page.
+``request.dbession.add``. We then redirect back to the ``view_page`` view for
+the newly created page.
If the view execution is *not* a result of a form submission (i.e., the
expression ``'form.submitted' in request.params`` is ``False``), the view
callable renders a template. To do so, it generates a ``save_url`` which the
template uses as the form post URL during rendering. We're lazy here, so
-we're going to use the same template (``templates/edit.pt``) for the add
-view as well as the page edit view. To do so we create a dummy Page object
+we're going to use the same template (``templates/edit.jinja2``) for the add
+view as well as the page edit view. To do so we create a dummy ``Page`` object
in order to satisfy the edit form's desire to have *some* page object
exposed as ``page``. :app:`Pyramid` will render the template associated
with this view to a response.
@@ -207,14 +208,14 @@ The ``edit_page`` view function
Here is the code for the ``edit_page`` view function and its decorator:
-.. literalinclude:: src/views/tutorial/views.py
- :lines: 60-72
- :lineno-start: 60
+.. literalinclude:: src/views/tutorial/views/default.py
+ :lines: 57-69
+ :lineno-start: 57
:linenos:
:language: python
``edit_page()`` is invoked when a user clicks the "Edit this
-Page" button on the view form. It renders an edit form but it also acts as
+Page" button on the view form. It renders an edit form, but it also acts as
the handler for the form it renders. The ``matchdict`` attribute of the
request passed to the ``edit_page`` view will have a ``'pagename'`` key
matching the name of the page the user wants to edit.
@@ -234,49 +235,51 @@ Adding templates
================
The ``view_page``, ``add_page`` and ``edit_page`` views that we've added
-reference a :term:`template`. Each template is a :term:`Chameleon`
-:term:`ZPT` template. These templates will live in the ``templates``
-directory of our tutorial package. Chameleon templates must have a ``.pt``
-extension to be recognized as such.
+reference a :term:`template`. Each template is a :term:`Jinja2` template.
+These templates will live in the ``templates`` directory of our tutorial
+package. Jinja2 templates must have a ``.jinja2`` extension to be recognized
+as such.
-The ``view.pt`` template
-------------------------
+The ``view.jinja2`` template
+----------------------------
-Create ``tutorial/tutorial/templates/view.pt`` and add the following
+Create ``tutorial/tutorial/templates/view.jinja2`` and add the following
content:
-.. literalinclude:: src/views/tutorial/templates/view.pt
+.. literalinclude:: src/views/tutorial/templates/view.jinja2
:linenos:
+ :emphasize-lines: 36,38-40
:language: html
This template is used by ``view_page()`` for displaying a single
wiki page. It includes:
-- A ``div`` element that is replaced with the ``content`` value provided by
- the view (lines 36-38). ``content`` contains HTML, so the ``structure``
- keyword is used to prevent escaping it (i.e., changing ">" to ">", etc.)
-- A link that points at the "edit" URL which invokes the ``edit_page`` view
- for the page being viewed (lines 40-42).
+- A variable that is replaced with the ``content`` value provided by the view
+ (line 36). ``content`` contains HTML, so the ``|safe`` filter is used to
+ prevent escaping it (e.g., changing ">" to ">").
+- A link that points at the "edit" URL which invokes the ``edit_page`` view for
+ the page being viewed (lines 38-40).
-The ``edit.pt`` template
-------------------------
+The ``edit.jinja2`` template
+----------------------------
-Create ``tutorial/tutorial/templates/edit.pt`` and add the following
+Create ``tutorial/tutorial/templates/edit.jinja2`` and add the following
content:
-.. literalinclude:: src/views/tutorial/templates/edit.pt
+.. literalinclude:: src/views/tutorial/templates/edit.jinja2
:linenos:
+ :emphasize-lines: 42,44,47
:language: html
This template is used by ``add_page()`` and ``edit_page()`` for adding and
editing a wiki page. It displays a page containing a form that includes:
-- A 10 row by 60 column ``textarea`` field named ``body`` that is filled
- with any existing page data when it is rendered (line 45).
-- A submit button that has the name ``form.submitted`` (line 48).
+- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with
+ any existing page data when it is rendered (line 44).
+- A submit button that has the name ``form.submitted`` (line 47).
The form POSTs back to the ``save_url`` argument supplied by the view (line
-43). The view will use the ``body`` and ``form.submitted`` values.
+42). The view will use the ``body`` and ``form.submitted`` values.
.. note:: Our templates use a ``request`` object that none of our tutorial
views return in their dictionary. ``request`` is one of several names that
@@ -284,7 +287,7 @@ The form POSTs back to the ``save_url`` argument supplied by the view (line
See :ref:`renderer_system_values` for information about other names that
are available by default when a template is used as a renderer.
-Static Assets
+Static assets
-------------
Our templates name static assets, including CSS and images. We don't need
@@ -299,7 +302,7 @@ subdirectories) and are just referred to by URL or by using the convenience
method ``static_url``, e.g.,
``request.static_url('<package>:static/foo.css')`` within templates.
-Adding Routes to ``__init__.py``
+Adding routes to ``__init__.py``
================================
The ``__init__.py`` file contains
@@ -340,7 +343,7 @@ something like:
.. literalinclude:: src/views/tutorial/__init__.py
:linenos:
- :emphasize-lines: 19-22
+ :emphasize-lines: 11-14
:language: python
The highlighted lines are the ones that need to be added or edited.
diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst
index 52f2ce7a5..8e3bb4c13 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -2,9 +2,8 @@
Design
======
-Following is a quick overview of the design of our wiki application, to help
-us understand the changes that we will be making as we work through the
-tutorial.
+Following is a quick overview of the design of our wiki application to help us
+understand the changes that we will be making as we work through the tutorial.
Overall
-------
@@ -17,17 +16,17 @@ Python module. We will add this module in the dependency list on the project
Models
------
-We'll be using a SQLite database to hold our wiki data, and we'll be using
+We'll be using an SQLite database to hold our wiki data, and we'll be using
:term:`SQLAlchemy` to access the data in this database.
Within the database, we define a single table named `pages`, whose elements
will store the wiki pages. There are two columns: `name` and `data`.
-URLs like ``/PageName`` will try to find an element in
-the table that has a corresponding name.
+URLs like ``/PageName`` will try to find an element in the table that has a
+corresponding name.
-To add a page to the wiki, a new row is created and the text
-is stored in `data`.
+To add a page to the wiki, a new row is created and the text is stored in
+`data`.
A page named ``FrontPage`` containing the text *This is the front page*, will
be created when the storage is initialized, and will be used as the wiki home
@@ -36,16 +35,14 @@ page.
Views
-----
-There will be three views to handle the normal operations of adding,
-editing, and viewing wiki pages, plus one view for the wiki front page.
-Two templates will be used, one for viewing, and one for both adding
-and editing wiki pages.
+There will be three views to handle the normal operations of adding, editing,
+and viewing wiki pages, plus one view for the wiki front page. Two templates
+will be used, one for viewing, and one for both adding and editing wiki pages.
-The default templating systems in :app:`Pyramid` are
-:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of
-:term:`ZPT`, which is an XML-based templating language. Mako is a
-non-XML-based templating language. Because we had to pick one,
-we chose Chameleon for this tutorial.
+As of version 1.5 :app:`Pyramid` no longer ships with templating systems. In
+this tutorial, we will use :term:`Jinja2`. Jinja2 is a modern and
+designer-friendly templating language for Python, modeled after Django's
+templates.
Security
--------
@@ -53,14 +50,14 @@ Security
We'll eventually be adding security to our application. The components we'll
use to do this are below.
-- USERS, a dictionary mapping :term:`userids <userid>` to their
- corresponding passwords.
+- USERS, a dictionary mapping :term:`userids <userid>` to their corresponding
+ passwords.
-- GROUPS, a dictionary mapping :term:`userids <userid>` to a
- list of groups to which they belong.
+- GROUPS, a dictionary mapping :term:`userids <userid>` to a list of groups to
+ which they belong.
-- ``groupfinder``, an *authorization callback* that looks up USERS and
- GROUPS. It will be provided in a new ``security.py`` file.
+- ``groupfinder``, an *authorization callback* that looks up USERS and GROUPS.
+ It will be provided in a new ``security/default.py`` subpackage and file.
- An :term:`ACL` is attached to the root :term:`resource`. Each row below
details an :term:`ACE`:
@@ -76,75 +73,72 @@ use to do this are below.
- Permission declarations are added to the views to assert the security
policies as each request is handled.
-Two additional views and one template will handle the login and
-logout tasks.
+Two additional views and one template will handle the login and logout tasks.
Summary
-------
-The URL, actions, template and permission associated to each view are
-listed in the following table:
-
-+----------------------+-----------------------+-------------+------------+------------+
-| URL | Action | View | Template | Permission |
-| | | | | |
-+======================+=======================+=============+============+============+
-| / | Redirect to | view_wiki | | |
-| | /FrontPage | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /PageName | Display existing | view_page | view.pt | view |
-| | page [2]_ | [1]_ | | |
-| | | | | |
-| | | | | |
-| | | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /PageName/edit_page | Display edit form | edit_page | edit.pt | edit |
-| | with existing | | | |
-| | content. | | | |
-| | | | | |
-| | If the form was | | | |
-| | submitted, redirect | | | |
-| | to /PageName | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /add_page/PageName | Create the page | add_page | edit.pt | edit |
-| | *PageName* in | | | |
-| | storage, display | | | |
-| | the edit form | | | |
-| | without content. | | | |
-| | | | | |
-| | If the form was | | | |
-| | submitted, | | | |
-| | redirect to | | | |
-| | /PageName | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /login | Display login form, | login | login.pt | |
-| | Forbidden [3]_ | | | |
-| | | | | |
-| | If the form was | | | |
-| | submitted, | | | |
-| | authenticate. | | | |
-| | | | | |
-| | - If authentication | | | |
-| | succeeds, | | | |
-| | redirect to the | | | |
-| | page that we | | | |
-| | came from. | | | |
-| | | | | |
-| | - If authentication | | | |
-| | fails, display | | | |
-| | login form with | | | |
-| | "login failed" | | | |
-| | message. | | | |
-| | | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-| /logout | Redirect to | logout | | |
-| | /FrontPage | | | |
-+----------------------+-----------------------+-------------+------------+------------+
-
-.. [1] This is the default view for a Page context
- when there is no view name.
-.. [2] Pyramid will return a default 404 Not Found page
- if the page *PageName* does not exist yet.
-.. [3] ``pyramid.exceptions.Forbidden`` is reached when a
- user tries to invoke a view that is
- not authorized by the authorization policy.
+The URL, actions, template, and permission associated to each view are listed
+in the following table:
+
++----------------------+-----------------------+-------------+----------------+------------+
+| URL | Action | View | Template | Permission |
+| | | | | |
++======================+=======================+=============+================+============+
+| / | Redirect to | view_wiki | | |
+| | /FrontPage | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /PageName | Display existing | view_page | view.jinja2 | view |
+| | page [2]_ | [1]_ | | |
+| | | | | |
+| | | | | |
+| | | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /PageName/edit_page | Display edit form | edit_page | edit.jinja2 | edit |
+| | with existing | | | |
+| | content. | | | |
+| | | | | |
+| | If the form was | | | |
+| | submitted, redirect | | | |
+| | to /PageName | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /add_page/PageName | Create the page | add_page | edit.jinja2 | edit |
+| | *PageName* in | | | |
+| | storage, display | | | |
+| | the edit form | | | |
+| | without content. | | | |
+| | | | | |
+| | If the form was | | | |
+| | submitted, | | | |
+| | redirect to | | | |
+| | /PageName | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /login | Display login form, | login | login.jinja2 | |
+| | Forbidden [3]_ | | | |
+| | | | | |
+| | If the form was | | | |
+| | submitted, | | | |
+| | authenticate. | | | |
+| | | | | |
+| | - If authentication | | | |
+| | succeeds, | | | |
+| | redirect to the | | | |
+| | page that we | | | |
+| | came from. | | | |
+| | | | | |
+| | - If authentication | | | |
+| | fails, display | | | |
+| | login form with | | | |
+| | "login failed" | | | |
+| | message. | | | |
+| | | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+| /logout | Redirect to | logout | | |
+| | /FrontPage | | | |
++----------------------+-----------------------+-------------+----------------+------------+
+
+.. [1] This is the default view for a Page context when there is no view name.
+.. [2] Pyramid will return a default 404 Not Found page if the page *PageName*
+ does not exist yet.
+.. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke
+ a view that is not authorized by the authorization policy.
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index 595dbd940..047c66c06 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -94,11 +94,11 @@ Install SQLite3 and its development packages
If you used a package manager to install your Python or if you compiled
your Python from source, then you must install SQLite3 and its
development packages. If you downloaded your Python as an installer
-from python.org, then you already have it installed and can proceed to
-the next section :ref:`sql_making_a_project`..
+from https://www.python.org, then you already have it installed and can
+proceed to the next section :ref:`sql_making_a_project`.
If you need to install the SQLite3 packages, then, for example, using
-the Debian system and apt-get, the command would be the following:
+the Debian system and ``apt-get``, the command would be the following:
.. code-block:: text
@@ -133,8 +133,8 @@ the :term:`scaffold` named ``alchemy`` which generates an application
that uses :term:`SQLAlchemy` and :term:`URL dispatch`.
:app:`Pyramid` supplies a variety of scaffolds to generate sample
-projects. We will use `pcreate`—a script that comes with Pyramid to
-quickly and easily generate scaffolds, usually with a single command—to
+projects. We will use `pcreate` — a script that comes with Pyramid to
+quickly and easily generate scaffolds, usually with a single command — to
create the scaffold for our project.
By passing `alchemy` into the `pcreate` command, the script creates
@@ -225,7 +225,7 @@ For a successful test run, you should see output that ends like this::
.
----------------------------------------------------------------------
Ran 1 test in 0.094s
-
+
OK
Expose test coverage information
@@ -383,8 +383,8 @@ This means the server is ready to accept requests.
Visit the application in a browser
==================================
-In a browser, visit `http://localhost:6543/ <http://localhost:6543>`_. You
-will see the generated application's default page.
+In a browser, visit http://localhost:6543/. You will see the generated
+application's default page.
One thing you'll notice is the "debug toolbar" icon on right hand side of the
page. You can read more about the purpose of the icon at
@@ -401,7 +401,7 @@ assumptions:
- you are willing to use :term:`URL dispatch` to map URLs to code
-- you want to use ``ZopeTransactionExtension`` and ``pyramid_tm`` to scope
+- you want to use ``zope.sqlalchemy`` and ``pyramid_tm`` to scope
sessions to requests
.. note::
diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini
index a9d53b296..99c4ff0fe 100644
--- a/docs/tutorials/wiki2/src/authorization/development.ini
+++ b/docs/tutorials/wiki2/src/authorization/development.ini
@@ -27,7 +27,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini
index 4684d2f7a..97acfbd7d 100644
--- a/docs/tutorials/wiki2/src/authorization/production.ini
+++ b/docs/tutorials/wiki2/src/authorization/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -16,7 +21,10 @@ 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, tutorial, sqlalchemy
@@ -51,6 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py
index 09bd63d33..d4e5a4072 100644
--- a/docs/tutorials/wiki2/src/authorization/setup.py
+++ b/docs/tutorials/wiki2/src/authorization/setup.py
@@ -10,7 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
index 2ada42171..084fee19f 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py
@@ -2,30 +2,20 @@ from pyramid.config import Configurator
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
-from sqlalchemy import engine_from_config
-
-from tutorial.security import groupfinder
-
-from .models import (
- DBSession,
- Base,
- )
-
+from security.default import groupfinder
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
- root_factory='tutorial.models.RootFactory')
+ root_factory='tutorial.models.mymodel.RootFactory')
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
- config.include('pyramid_chameleon')
+ config.include('pyramid_jinja2')
+ config.include('.models.meta')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('login', '/login')
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
new file mode 100644
index 000000000..7b1c62867
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/__init__.py
@@ -0,0 +1,7 @@
+from sqlalchemy.orm import configure_mappers
+# import all models classes here for sqlalchemy mappers
+# to pick up
+from .mymodel import Page # flake8: noqa
+
+# run configure mappers to ensure we avoid any race conditions
+configure_mappers()
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py
new file mode 100644
index 000000000..80ececd8c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/meta.py
@@ -0,0 +1,49 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.schema import MetaData
+import zope.sqlalchemy
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
+
+
+def includeme(config):
+ settings = config.get_settings()
+ dbmaker = get_dbmaker(get_engine(settings))
+
+ config.add_request_method(
+ lambda r: get_session(r.tm, dbmaker),
+ 'dbsession',
+ reify=True
+ )
+
+ config.include('pyramid_tm')
+
+
+def get_session(transaction_manager, dbmaker):
+ dbsession = dbmaker()
+ zope.sqlalchemy.register(dbsession,
+ transaction_manager=transaction_manager)
+ return dbsession
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_dbmaker(engine):
+ dbmaker = sessionmaker()
+ dbmaker.configure(bind=engine)
+ return dbmaker
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models/mymodel.py
index 4f7e1e024..03e2f90ca 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models/mymodel.py
@@ -1,3 +1,5 @@
+from .meta import Base
+
from pyramid.security import (
Allow,
Everyone,
@@ -9,29 +11,16 @@ from sqlalchemy import (
Text,
)
-from sqlalchemy.ext.declarative import declarative_base
-
-from sqlalchemy.orm import (
- scoped_session,
- sessionmaker,
- )
-
-from zope.sqlalchemy import ZopeTransactionExtension
-
-DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
-Base = declarative_base()
-
class Page(Base):
""" The SQLAlchemy declarative model class for a Page object. """
__tablename__ = 'pages'
id = Column(Integer, primary_key=True)
name = Column(Text, unique=True)
- data = Column(Text)
-
+ data = Column(Integer)
class RootFactory(object):
__acl__ = [ (Allow, Everyone, 'view'),
(Allow, 'group:editors', 'edit') ]
def __init__(self, request):
- pass
+ pass \ No newline at end of file
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py
index 23a5f13f4..4aac4a848 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py
@@ -2,36 +2,41 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
-from ..models import (
- DBSession,
- Page,
+from ..models.meta import (
Base,
+ get_session,
+ get_engine,
+ get_dbmaker,
)
+from ..models.mymodel import Page
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
setup_logging(config_uri)
settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+
+ engine = get_engine(settings)
+ dbmaker = get_dbmaker(engine)
+
+ dbsession = get_session(transaction.manager, dbmaker)
+
Base.metadata.create_all(engine)
+
with transaction.manager:
model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
+ dbsession.add(model)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/security/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/security/__init__.py
new file mode 100644
index 000000000..5bb534f79
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/security/__init__.py
@@ -0,0 +1 @@
+# package
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/security.py b/docs/tutorials/wiki2/src/authorization/tutorial/security/default.py
index d88c9c71f..d88c9c71f 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/security.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/security/default.py
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css
index 2f924bcc5..0d25de5b6 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css
@@ -1 +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
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.jinja2
index 50e55c850..c4f3a2c93 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>Edit{% if page.name %} {{page.name}}{% endif %} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,31 +22,31 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
+ {% if logged_in %}
+ <p class="pull-right">
+ <a href="{{ request.application_url }}/logout">Logout</a>
+ </p>
+ {% endif %}
<p>
- Editing <strong><span tal:replace="page.name">Page Name Goes
- Here</span></strong>
+ Editing <strong>{% if page.name %}{{page.name}}{% else %}Page Name Goes Here{% endif %}</strong>
</p>
<p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <p class="pull-right">
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
+ <a href="{{request.application_url}}">FrontPage</a>.
</p>
- <form action="${save_url}" method="post">
+ <form action="{{ save_url }}" method="post">
<div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ <textarea class="form-control" name="body" rows="10" cols="60">{{ page.data }}</textarea>
</div>
<div class="form-group">
<button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
index c9b0cec21..ff624c65b 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/layout.jinja2
@@ -1,12 +1,12 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
<title>Alchemy Scaffold for The Pyramid Web Framework</title>
@@ -14,7 +14,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7.dev0</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.jinja2
index 4a938e9bb..a80a2a165 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>Login - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>Login - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,13 +22,14 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
@@ -37,17 +37,17 @@
<strong>
Login
</strong><br>
- <span tal:replace="message"></span>
+ {{ message }}
</p>
- <form action="${url}" method="post">
- <input type="hidden" name="came_from" value="${came_from}">
+ <form action="{{ url }}" method="post">
+ <input type="hidden" name="came_from" value="{{ came_from }}">
<div class="form-group">
<label for="login">Username</label>
- <input type="text" name="login" value="${login}">
+ <input type="text" name="login" value="{{ login }}">
</div>
<div class="form-group">
<label for="password">Password</label>
- <input type="password" name="password" value="${password}">
+ <input type="password" name="password" value="{{ password }}">
</div>
<div class="form-group">
<button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.jinja2
new file mode 100644
index 000000000..bb622bf5a
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7.dev0</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.jinja2
index 4e5772de0..a7afc66fc 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>{{page.name}} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,35 +22,33 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
- <div tal:replace="structure content">
- Page text goes here.
- </div>
+ {% if logged_in %}
+ <p class="pull-right">
+ <a href="{{ request.application_url }}/logout">Logout</a>
+ </p>
+ {% endif %}
+ <p>{{ content|safe }}</p>
<p>
- <a tal:attributes="href edit_url" href="">
+ <a href="{{ edit_url }}">
Edit this page
</a>
</p>
<p>
- Viewing <strong><span tal:replace="page.name">
- Page Name Goes Here</span></strong>
+ Viewing <strong>{% if page.name %}{{page.name}}{% else %}Page Name Goes Here{% endif %}</strong>
</p>
<p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
- </p>
- <p class="pull-right">
- <span tal:condition="logged_in">
- <a href="${request.application_url}/logout">Logout</a>
- </span>
+ <a href="{{request.application_url}}">FrontPage</a>.
</p>
</div>
</div>
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py
index 9f01d2da5..b947e3bb1 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py
@@ -3,144 +3,63 @@ import transaction
from pyramid import testing
-def _initTestingDB():
- from sqlalchemy import create_engine
- from tutorial.models import (
- DBSession,
- Page,
- Base
- )
- engine = create_engine('sqlite://')
- Base.metadata.create_all(engine)
- DBSession.configure(bind=engine)
- with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
- return DBSession
-
-def _registerRoutes(config):
- config.add_route('view_page', '{pagename}')
- config.add_route('edit_page', '{pagename}/edit_page')
- config.add_route('add_page', 'add_page/{pagename}')
-
-class ViewWikiTests(unittest.TestCase):
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- self.session = _initTestingDB()
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models.meta')
+ settings = self.config.get_settings()
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
+ from .models.meta import (
+ get_session,
+ get_engine,
+ get_dbmaker,
+ )
- def _callFUT(self, request):
- from tutorial.views import view_wiki
- return view_wiki(request)
+ self.engine = get_engine(settings)
+ dbmaker = get_dbmaker(self.engine)
- def test_it(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
+ self.session = get_session(transaction.manager, dbmaker)
-class ViewPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import view_page
- return view_page(request)
-
- def test_it(self):
- from tutorial.models import Page
- request = testing.DummyRequest()
- request.matchdict['pagename'] = 'IDoExist'
- page = Page(name='IDoExist', data='Hello CruelWorld IDoExist')
- self.session.add(page)
- _registerRoutes(self.config)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(
- info['content'],
- '<div class="document">\n'
- '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
- 'CruelWorld</a> '
- '<a href="http://example.com/IDoExist">'
- 'IDoExist</a>'
- '</p>\n</div>\n')
- self.assertEqual(info['edit_url'],
- 'http://example.com/IDoExist/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ from .models.meta import Base
- def tearDown(self):
- self.session.remove()
testing.tearDown()
+ transaction.abort()
+ Base.metadata.create_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def _callFUT(self, request):
- from tutorial.views import add_page
- return add_page(request)
-
- def test_it_notsubmitted(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'AnotherPage'}
- info = self._callFUT(request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(info['save_url'],
- 'http://example.com/add_page/AnotherPage')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'AnotherPage'}
- self._callFUT(request)
- page = self.session.query(Page).filter_by(name='AnotherPage').one()
- self.assertEqual(page.data, 'Hello yo!')
-
-class EditPageTests(unittest.TestCase):
def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
+ from .models.mymodel import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info['one'].name, 'one')
+ self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
- def _callFUT(self, request):
- from tutorial.views import edit_page
- return edit_page(request)
-
- def test_it_notsubmitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(info['save_url'],
- 'http://example.com/abc/edit_page')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/abc')
- self.assertEqual(page.data, 'Hello yo!')
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
index 41bea4785..f35f041a4 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views/default.py
@@ -1,3 +1,4 @@
+import cgi
import re
from docutils.core import publish_parts
@@ -16,13 +17,9 @@ from pyramid.security import (
forget,
)
-from .security import USERS
-
-from .models import (
- DBSession,
- Page,
- )
+from ..security.default import USERS
+from ..models.mymodel import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@@ -30,26 +27,26 @@ wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@view_config(route_name='view_wiki',
permission='view')
def view_wiki(request):
- return HTTPFound(location = request.route_url('view_page',
- pagename='FrontPage'))
+ return HTTPFound(location=request.route_url('view_page',
+ pagename='FrontPage'))
-@view_config(route_name='view_page', renderer='templates/view.pt',
+@view_config(route_name='view_page', renderer='templates/view.jinja2',
permission='view')
def view_page(request):
pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).first()
+ page = request.dbsession.query(Page).filter_by(name=pagename).first()
if page is None:
return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
- exists = DBSession.query(Page).filter_by(name=word).all()
+ exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, 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)
@@ -57,14 +54,14 @@ def view_page(request):
return dict(page=page, content=content, edit_url=edit_url,
logged_in=request.authenticated_userid)
-@view_config(route_name='add_page', renderer='templates/edit.pt',
+@view_config(route_name='add_page', renderer='templates/edit.jinja2',
permission='edit')
def add_page(request):
pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(name=pagename, data=body)
- DBSession.add(page)
+ request.dbsession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
save_url = request.route_url('add_page', pagename=pagename)
@@ -72,24 +69,24 @@ def add_page(request):
return dict(page=page, save_url=save_url,
logged_in=request.authenticated_userid)
-@view_config(route_name='edit_page', renderer='templates/edit.pt',
+@view_config(route_name='edit_page', renderer='templates/edit.jinja2',
permission='edit')
def edit_page(request):
pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).one()
+ page = request.dbsession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
- DBSession.add(page)
+ request.dbsession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
return dict(
page=page,
- save_url=request.route_url('edit_page', pagename=pagename),
+ save_url = request.route_url('edit_page', pagename=pagename),
logged_in=request.authenticated_userid
)
-@view_config(route_name='login', renderer='templates/login.pt')
-@forbidden_view_config(renderer='templates/login.pt')
+@view_config(route_name='login', renderer='templates/login.jinja2')
+@forbidden_view_config(renderer='templates/login.jinja2')
def login(request):
login_url = request.route_url('login')
referrer = request.url
diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini
index a9d53b296..99c4ff0fe 100644
--- a/docs/tutorials/wiki2/src/basiclayout/development.ini
+++ b/docs/tutorials/wiki2/src/basiclayout/development.ini
@@ -27,7 +27,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini
index fa94c1b3e..97acfbd7d 100644
--- a/docs/tutorials/wiki2/src/basiclayout/production.ini
+++ b/docs/tutorials/wiki2/src/basiclayout/production.ini
@@ -59,4 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py
index 15e7e5923..eb771010f 100644
--- a/docs/tutorials/wiki2/src/basiclayout/setup.py
+++ b/docs/tutorials/wiki2/src/basiclayout/setup.py
@@ -10,7 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
index 867049e4f..7994bbfa8 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py
@@ -1,20 +1,12 @@
from pyramid.config import Configurator
-from sqlalchemy import engine_from_config
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
+ config.include('pyramid_jinja2')
+ config.include('.models.meta')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py
deleted file mode 100644
index 11ddccadb..000000000
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from sqlalchemy import (
- Column,
- Integer,
- Text,
- Index,
- )
-
-from sqlalchemy.ext.declarative import declarative_base
-
-from sqlalchemy.orm import (
- scoped_session,
- sessionmaker,
- )
-
-from zope.sqlalchemy import ZopeTransactionExtension
-
-DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
-Base = declarative_base()
-
-
-class MyModel(Base):
- __tablename__ = 'models'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- value = Column(Integer)
-
-Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
new file mode 100644
index 000000000..6ffc10a78
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/__init__.py
@@ -0,0 +1,7 @@
+from sqlalchemy.orm import configure_mappers
+# import all models classes here for sqlalchemy mappers
+# to pick up
+from .mymodel import MyModel # flake8: noqa
+
+# run configure mappers to ensure we avoid any race conditions
+configure_mappers()
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py
new file mode 100644
index 000000000..80ececd8c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/meta.py
@@ -0,0 +1,49 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.schema import MetaData
+import zope.sqlalchemy
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
+
+
+def includeme(config):
+ settings = config.get_settings()
+ dbmaker = get_dbmaker(get_engine(settings))
+
+ config.add_request_method(
+ lambda r: get_session(r.tm, dbmaker),
+ 'dbsession',
+ reify=True
+ )
+
+ config.include('pyramid_tm')
+
+
+def get_session(transaction_manager, dbmaker):
+ dbsession = dbmaker()
+ zope.sqlalchemy.register(dbsession,
+ transaction_manager=transaction_manager)
+ return dbsession
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_dbmaker(engine):
+ dbmaker = sessionmaker()
+ dbmaker.configure(bind=engine)
+ return dbmaker
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py
new file mode 100644
index 000000000..5a2b5890c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models/mymodel.py
@@ -0,0 +1,17 @@
+from .meta import Base
+from sqlalchemy import (
+ Column,
+ Index,
+ Integer,
+ Text,
+)
+
+
+class MyModel(Base):
+ __tablename__ = 'models'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text)
+ value = Column(Integer)
+
+
+Index('my_index', MyModel.name, unique=True, mysql_length=255)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py
index 66feb3008..f0d09729e 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py
@@ -2,36 +2,44 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
-from ..models import (
- DBSession,
- MyModel,
+from pyramid.scripts.common import parse_vars
+
+from ..models.meta import (
Base,
+ get_session,
+ get_engine,
+ get_dbmaker,
)
+from ..models.mymodel import MyModel
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
+ options = parse_vars(argv[2:])
setup_logging(config_uri)
- settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+ settings = get_appsettings(config_uri, options=options)
+
+ engine = get_engine(settings)
+ dbmaker = get_dbmaker(engine)
+
+ dbsession = get_session(transaction.manager, dbmaker)
+
Base.metadata.create_all(engine)
+
with transaction.manager:
model = MyModel(name='one', value=1)
- DBSession.add(model)
+ dbsession.add(model)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css
index 2f924bcc5..0d25de5b6 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css
@@ -1 +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
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
index c9b0cec21..ff624c65b 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/layout.jinja2
@@ -1,12 +1,12 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
<title>Alchemy Scaffold for The Pyramid Web Framework</title>
@@ -14,7 +14,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7.dev0</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
new file mode 100644
index 000000000..bb622bf5a
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7.dev0</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
index 57a775e0a..b947e3bb1 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py
@@ -3,31 +3,63 @@ import transaction
from pyramid import testing
-from .models import DBSession
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
-class TestMyView(unittest.TestCase):
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- from sqlalchemy import create_engine
- engine = create_engine('sqlite://')
- from .models import (
- Base,
- MyModel,
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models.meta')
+ settings = self.config.get_settings()
+
+ from .models.meta import (
+ get_session,
+ get_engine,
+ get_dbmaker,
)
- DBSession.configure(bind=engine)
- Base.metadata.create_all(engine)
- with transaction.manager:
- model = MyModel(name='one', value=55)
- DBSession.add(model)
+
+ self.engine = get_engine(settings)
+ dbmaker = get_dbmaker(self.engine)
+
+ self.session = get_session(transaction.manager, dbmaker)
+
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- DBSession.remove()
+ from .models.meta import Base
+
testing.tearDown()
+ transaction.abort()
+ Base.metadata.create_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def test_it(self):
- from .views import my_view
- request = testing.DummyRequest()
- info = my_view(request)
+ def setUp(self):
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
+
+ from .models.mymodel import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
+
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py
index 4cfcae4af..13ad8793c 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views/default.py
@@ -3,26 +3,25 @@ from pyramid.view import view_config
from sqlalchemy.exc import DBAPIError
-from .models import (
- DBSession,
- MyModel,
- )
+from ..models.mymodel import MyModel
-@view_config(route_name='home', renderer='templates/mytemplate.pt')
+@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
def my_view(request):
try:
- one = DBSession.query(MyModel).filter(MyModel.name == 'one').first()
+ query = request.dbsession.query(MyModel)
+ one = query.filter(MyModel.name == 'one').first()
except DBAPIError:
- return Response(conn_err_msg, content_type='text/plain', status_int=500)
+ return Response(db_err_msg, content_type='text/plain', status_int=500)
return {'one': one, 'project': 'tutorial'}
-conn_err_msg = """\
+
+db_err_msg = """\
Pyramid is having a problem using your SQL database. The problem
might be caused by one of the following things:
1. You may need to run the "initialize_tutorial_db" script
- to initialize your database tables. Check your virtual
+ to initialize your database tables. Check your virtual
environment's "bin" directory for this script and try to run it.
2. Your database server may not be running. Check that the
@@ -32,4 +31,3 @@ might be caused by one of the following things:
After you fix the problem, please restart the Pyramid application to
try it again.
"""
-
diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini
index a9d53b296..99c4ff0fe 100644
--- a/docs/tutorials/wiki2/src/models/development.ini
+++ b/docs/tutorials/wiki2/src/models/development.ini
@@ -27,7 +27,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini
index 4684d2f7a..97acfbd7d 100644
--- a/docs/tutorials/wiki2/src/models/production.ini
+++ b/docs/tutorials/wiki2/src/models/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -16,7 +21,10 @@ 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, tutorial, sqlalchemy
@@ -51,6 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py
index 15e7e5923..eb771010f 100644
--- a/docs/tutorials/wiki2/src/models/setup.py
+++ b/docs/tutorials/wiki2/src/models/setup.py
@@ -10,7 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
index 867049e4f..7994bbfa8 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py
@@ -1,20 +1,12 @@
from pyramid.config import Configurator
-from sqlalchemy import engine_from_config
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
+ config.include('pyramid_jinja2')
+ config.include('.models.meta')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.scan()
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py
deleted file mode 100644
index f028c917a..000000000
--- a/docs/tutorials/wiki2/src/models/tutorial/models.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from sqlalchemy import (
- Column,
- Integer,
- Text,
- )
-
-from sqlalchemy.ext.declarative import declarative_base
-
-from sqlalchemy.orm import (
- scoped_session,
- sessionmaker,
- )
-
-from zope.sqlalchemy import ZopeTransactionExtension
-
-DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
-Base = declarative_base()
-
-
-class Page(Base):
- """ The SQLAlchemy declarative model class for a Page object. """
- __tablename__ = 'pages'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- data = Column(Text)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
new file mode 100644
index 000000000..7b1c62867
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/__init__.py
@@ -0,0 +1,7 @@
+from sqlalchemy.orm import configure_mappers
+# import all models classes here for sqlalchemy mappers
+# to pick up
+from .mymodel import Page # flake8: noqa
+
+# run configure mappers to ensure we avoid any race conditions
+configure_mappers()
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/meta.py b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py
new file mode 100644
index 000000000..80ececd8c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/meta.py
@@ -0,0 +1,49 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.schema import MetaData
+import zope.sqlalchemy
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
+
+
+def includeme(config):
+ settings = config.get_settings()
+ dbmaker = get_dbmaker(get_engine(settings))
+
+ config.add_request_method(
+ lambda r: get_session(r.tm, dbmaker),
+ 'dbsession',
+ reify=True
+ )
+
+ config.include('pyramid_tm')
+
+
+def get_session(transaction_manager, dbmaker):
+ dbsession = dbmaker()
+ zope.sqlalchemy.register(dbsession,
+ transaction_manager=transaction_manager)
+ return dbsession
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_dbmaker(engine):
+ dbmaker = sessionmaker()
+ dbmaker.configure(bind=engine)
+ return dbmaker
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py
new file mode 100644
index 000000000..45571d78e
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/models/mymodel.py
@@ -0,0 +1,14 @@
+from .meta import Base
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+)
+
+
+class Page(Base):
+ """ The SQLAlchemy declarative model class for a Page object. """
+ __tablename__ = 'pages'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, unique=True)
+ data = Column(Integer)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py
index 23a5f13f4..4aac4a848 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py
@@ -2,36 +2,41 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
-from ..models import (
- DBSession,
- Page,
+from ..models.meta import (
Base,
+ get_session,
+ get_engine,
+ get_dbmaker,
)
+from ..models.mymodel import Page
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
setup_logging(config_uri)
settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+
+ engine = get_engine(settings)
+ dbmaker = get_dbmaker(engine)
+
+ dbsession = get_session(transaction.manager, dbmaker)
+
Base.metadata.create_all(engine)
+
with transaction.manager:
model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
+ dbsession.add(model)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css
index 2f924bcc5..0d25de5b6 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css
+++ b/docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css
@@ -1 +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
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
index c9b0cec21..ff624c65b 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/layout.jinja2
@@ -1,12 +1,12 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
<title>Alchemy Scaffold for The Pyramid Web Framework</title>
@@ -14,7 +14,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7.dev0</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
new file mode 100644
index 000000000..bb622bf5a
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7.dev0</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py
index 57a775e0a..b947e3bb1 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py
@@ -3,31 +3,63 @@ import transaction
from pyramid import testing
-from .models import DBSession
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
-class TestMyView(unittest.TestCase):
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- from sqlalchemy import create_engine
- engine = create_engine('sqlite://')
- from .models import (
- Base,
- MyModel,
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models.meta')
+ settings = self.config.get_settings()
+
+ from .models.meta import (
+ get_session,
+ get_engine,
+ get_dbmaker,
)
- DBSession.configure(bind=engine)
- Base.metadata.create_all(engine)
- with transaction.manager:
- model = MyModel(name='one', value=55)
- DBSession.add(model)
+
+ self.engine = get_engine(settings)
+ dbmaker = get_dbmaker(self.engine)
+
+ self.session = get_session(transaction.manager, dbmaker)
+
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- DBSession.remove()
+ from .models.meta import Base
+
testing.tearDown()
+ transaction.abort()
+ Base.metadata.create_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def test_it(self):
- from .views import my_view
- request = testing.DummyRequest()
- info = my_view(request)
+ def setUp(self):
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
+
+ from .models.mymodel import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
self.assertEqual(info['one'].name, 'one')
self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
+
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/models/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/models/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/models/tutorial/views.py b/docs/tutorials/wiki2/src/models/tutorial/views/default.py
index 4cfcae4af..13ad8793c 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/views/default.py
@@ -3,26 +3,25 @@ from pyramid.view import view_config
from sqlalchemy.exc import DBAPIError
-from .models import (
- DBSession,
- MyModel,
- )
+from ..models.mymodel import MyModel
-@view_config(route_name='home', renderer='templates/mytemplate.pt')
+@view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
def my_view(request):
try:
- one = DBSession.query(MyModel).filter(MyModel.name == 'one').first()
+ query = request.dbsession.query(MyModel)
+ one = query.filter(MyModel.name == 'one').first()
except DBAPIError:
- return Response(conn_err_msg, content_type='text/plain', status_int=500)
+ return Response(db_err_msg, content_type='text/plain', status_int=500)
return {'one': one, 'project': 'tutorial'}
-conn_err_msg = """\
+
+db_err_msg = """\
Pyramid is having a problem using your SQL database. The problem
might be caused by one of the following things:
1. You may need to run the "initialize_tutorial_db" script
- to initialize your database tables. Check your virtual
+ to initialize your database tables. Check your virtual
environment's "bin" directory for this script and try to run it.
2. Your database server may not be running. Check that the
@@ -32,4 +31,3 @@ might be caused by one of the following things:
After you fix the problem, please restart the Pyramid application to
try it again.
"""
-
diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini
index a9d53b296..99c4ff0fe 100644
--- a/docs/tutorials/wiki2/src/tests/development.ini
+++ b/docs/tutorials/wiki2/src/tests/development.ini
@@ -27,7 +27,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini
index 4684d2f7a..97acfbd7d 100644
--- a/docs/tutorials/wiki2/src/tests/production.ini
+++ b/docs/tutorials/wiki2/src/tests/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -16,7 +21,10 @@ 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, tutorial, sqlalchemy
@@ -51,6 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index d8486e462..f640b4399 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -10,7 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
@@ -18,7 +18,7 @@ requires = [
'zope.sqlalchemy',
'waitress',
'docutils',
- 'WebTest', # add this
+ 'WebTest',
]
setup(name='tutorial',
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
index cee89184b..084fee19f 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
@@ -2,30 +2,20 @@ from pyramid.config import Configurator
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
-from sqlalchemy import engine_from_config
-
-from tutorial.security import groupfinder
-
-from .models import (
- DBSession,
- Base,
- )
-
+from security.default import groupfinder
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=groupfinder, hashalg='sha512')
authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings,
- root_factory='tutorial.models.RootFactory')
- config.include('pyramid_chameleon')
+ root_factory='tutorial.models.mymodel.RootFactory')
config.set_authentication_policy(authn_policy)
config.set_authorization_policy(authz_policy)
+ config.include('pyramid_jinja2')
+ config.include('.models.meta')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('login', '/login')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
new file mode 100644
index 000000000..7b1c62867
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/__init__.py
@@ -0,0 +1,7 @@
+from sqlalchemy.orm import configure_mappers
+# import all models classes here for sqlalchemy mappers
+# to pick up
+from .mymodel import Page # flake8: noqa
+
+# run configure mappers to ensure we avoid any race conditions
+configure_mappers()
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py b/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py
new file mode 100644
index 000000000..80ececd8c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/meta.py
@@ -0,0 +1,49 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.schema import MetaData
+import zope.sqlalchemy
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
+
+
+def includeme(config):
+ settings = config.get_settings()
+ dbmaker = get_dbmaker(get_engine(settings))
+
+ config.add_request_method(
+ lambda r: get_session(r.tm, dbmaker),
+ 'dbsession',
+ reify=True
+ )
+
+ config.include('pyramid_tm')
+
+
+def get_session(transaction_manager, dbmaker):
+ dbsession = dbmaker()
+ zope.sqlalchemy.register(dbsession,
+ transaction_manager=transaction_manager)
+ return dbsession
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_dbmaker(engine):
+ dbmaker = sessionmaker()
+ dbmaker.configure(bind=engine)
+ return dbmaker
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models.py b/docs/tutorials/wiki2/src/tests/tutorial/models/mymodel.py
index 4f7e1e024..03e2f90ca 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/mymodel.py
@@ -1,3 +1,5 @@
+from .meta import Base
+
from pyramid.security import (
Allow,
Everyone,
@@ -9,29 +11,16 @@ from sqlalchemy import (
Text,
)
-from sqlalchemy.ext.declarative import declarative_base
-
-from sqlalchemy.orm import (
- scoped_session,
- sessionmaker,
- )
-
-from zope.sqlalchemy import ZopeTransactionExtension
-
-DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
-Base = declarative_base()
-
class Page(Base):
""" The SQLAlchemy declarative model class for a Page object. """
__tablename__ = 'pages'
id = Column(Integer, primary_key=True)
name = Column(Text, unique=True)
- data = Column(Text)
-
+ data = Column(Integer)
class RootFactory(object):
__acl__ = [ (Allow, Everyone, 'view'),
(Allow, 'group:editors', 'edit') ]
def __init__(self, request):
- pass
+ pass \ No newline at end of file
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
index 23a5f13f4..4aac4a848 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py
@@ -2,36 +2,41 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
-from ..models import (
- DBSession,
- Page,
+from ..models.meta import (
Base,
+ get_session,
+ get_engine,
+ get_dbmaker,
)
+from ..models.mymodel import Page
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
setup_logging(config_uri)
settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+
+ engine = get_engine(settings)
+ dbmaker = get_dbmaker(engine)
+
+ dbsession = get_session(transaction.manager, dbmaker)
+
Base.metadata.create_all(engine)
+
with transaction.manager:
model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
+ dbsession.add(model)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/security/__init__.py
new file mode 100644
index 000000000..5bb534f79
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/security/__init__.py
@@ -0,0 +1 @@
+# package
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security.py b/docs/tutorials/wiki2/src/tests/tutorial/security/default.py
index d88c9c71f..d88c9c71f 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/security.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/security/default.py
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css b/docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css
index 2f924bcc5..0d25de5b6 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css
+++ b/docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css
@@ -1 +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
+@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2
index ed355434d..c4f3a2c93 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>Edit{% if page.name %} {{page.name}}{% endif %} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,29 +22,31 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
- <p tal:condition="logged_in" class="pull-right">
- <a href="${request.application_url}/logout">Logout</a>
+ {% if logged_in %}
+ <p class="pull-right">
+ <a href="{{ request.application_url }}/logout">Logout</a>
</p>
+ {% endif %}
<p>
- Editing <strong><span tal:replace="page.name">Page Name Goes
- Here</span></strong>
+ Editing <strong>{% if page.name %}{{page.name}}{% else %}Page Name Goes Here{% endif %}</strong>
</p>
<p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
+ <a href="{{request.application_url}}">FrontPage</a>.
</p>
- <form action="${save_url}" method="post">
+ <form action="{{ save_url }}" method="post">
<div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ <textarea class="form-control" name="body" rows="10" cols="60">{{ page.data }}</textarea>
</div>
<div class="form-group">
<button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
index c9b0cec21..ff624c65b 100644
--- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
@@ -1,12 +1,12 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
<title>Alchemy Scaffold for The Pyramid Web Framework</title>
@@ -14,7 +14,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -29,19 +29,19 @@
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
</div>
</div>
<div class="row">
<div class="links">
<ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
+ <li class="current-version">Generated by v1.7.dev0</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2
new file mode 100644
index 000000000..a80a2a165
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
+
+ <title>Login - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ <p>
+ <strong>
+ Login
+ </strong><br>
+ {{ message }}
+ </p>
+ <form action="{{ url }}" method="post">
+ <input type="hidden" name="came_from" value="{{ came_from }}">
+ <div class="form-group">
+ <label for="login">Username</label>
+ <input type="text" name="login" value="{{ login }}">
+ </div>
+ <div class="form-group">
+ <label for="password">Password</label>
+ <input type="password" name="password" value="{{ password }}">
+ </div>
+ <div class="form-group">
+ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt
deleted file mode 100644
index 5f8e9b98c..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt
+++ /dev/null
@@ -1,54 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
- xmlns:tal="http://xml.zope.org/namespaces/tal">
-<head>
- <title>Login - Pyramid tutorial wiki (based on TurboGears
- 20-Minute Wiki)</title>
- <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
- <meta name="keywords" content="python web application" />
- <meta name="description" content="pyramid web application" />
- <link rel="shortcut icon"
- href="${request.static_url('tutorial:static/favicon.ico')}" />
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/pylons.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <!--[if lte IE 6]>
- <link rel="stylesheet"
- href="${request.static_url('tutorial:static/ie6.css')}"
- type="text/css" media="screen" charset="utf-8" />
- <![endif]-->
-</head>
-<body>
- <div id="wrap">
- <div id="top-small">
- <div class="top-small align-center">
- <div>
- <img width="220" height="50" alt="pyramid"
- src="${request.static_url('tutorial:static/pyramid-small.png')}" />
- </div>
- </div>
- </div>
- <div id="middle">
- <div class="middle align-right">
- <div id="left" class="app-welcome align-left">
- <b>Login</b><br/>
- <span tal:replace="message"/>
- </div>
- <div id="right" class="app-welcome align-right"></div>
- </div>
- </div>
- <div id="bottom">
- <div class="bottom">
- <form action="${url}" method="post">
- <input type="hidden" name="came_from" value="${came_from}"/>
- <input type="text" name="login" value="${login}"/><br/>
- <input type="password" name="password"
- value="${password}"/><br/>
- <input type="submit" name="form.submitted" value="Log In"/>
- </form>
- </div>
- </div>
- </div>
-</body>
-</html>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.jinja2
new file mode 100644
index 000000000..bb622bf5a
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7.dev0</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2
index 02cb8e73b..a7afc66fc 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>{{page.name}} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,33 +22,33 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
- <p tal:condition="logged_in" class="pull-right">
- <a href="${request.application_url}/logout">Logout</a>
+ {% if logged_in %}
+ <p class="pull-right">
+ <a href="{{ request.application_url }}/logout">Logout</a>
</p>
- <div tal:replace="structure content">
- Page text goes here.
- </div>
+ {% endif %}
+ <p>{{ content|safe }}</p>
<p>
- <a tal:attributes="href edit_url" href="">
+ <a href="{{ edit_url }}">
Edit this page
</a>
</p>
<p>
- Viewing <strong><span tal:replace="page.name">
- Page Name Goes Here</span></strong>
+ Viewing <strong>{% if page.name %}{{page.name}}{% else %}Page Name Goes Here{% endif %}</strong>
</p>
<p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
+ <a href="{{request.application_url}}">FrontPage</a>.
</p>
</div>
</div>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests.py b/docs/tutorials/wiki2/src/tests/tutorial/tests.py
deleted file mode 100644
index c50e05b6d..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/tests.py
+++ /dev/null
@@ -1,235 +0,0 @@
-import unittest
-import transaction
-
-from pyramid import testing
-
-
-def _initTestingDB():
- from sqlalchemy import create_engine
- from tutorial.models import (
- DBSession,
- Page,
- Base
- )
- engine = create_engine('sqlite://')
- Base.metadata.create_all(engine)
- DBSession.configure(bind=engine)
- with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
- return DBSession
-
-
-def _registerRoutes(config):
- config.add_route('view_page', '{pagename}')
- config.add_route('edit_page', '{pagename}/edit_page')
- config.add_route('add_page', 'add_page/{pagename}')
-
-
-class ViewWikiTests(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import view_wiki
- return view_wiki(request)
-
- def test_it(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
-
-
-class ViewPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
-
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import view_page
- return view_page(request)
-
- def test_it(self):
- from tutorial.models import Page
- request = testing.DummyRequest()
- request.matchdict['pagename'] = 'IDoExist'
- page = Page(name='IDoExist', data='Hello CruelWorld IDoExist')
- self.session.add(page)
- _registerRoutes(self.config)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(
- info['content'],
- '<div class="document">\n'
- '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
- 'CruelWorld</a> '
- '<a href="http://example.com/IDoExist">'
- 'IDoExist</a>'
- '</p>\n</div>\n')
- self.assertEqual(info['edit_url'],
- 'http://example.com/IDoExist/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
-
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import add_page
- return add_page(request)
-
- def test_it_notsubmitted(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'AnotherPage'}
- info = self._callFUT(request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(info['save_url'],
- 'http://example.com/add_page/AnotherPage')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'AnotherPage'}
- self._callFUT(request)
- page = self.session.query(Page).filter_by(name='AnotherPage').one()
- self.assertEqual(page.data, 'Hello yo!')
-
-
-class EditPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
-
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import edit_page
- return edit_page(request)
-
- def test_it_notsubmitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(info['save_url'],
- 'http://example.com/abc/edit_page')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/abc')
- self.assertEqual(page.data, 'Hello yo!')
-
-
-class FunctionalTests(unittest.TestCase):
-
- viewer_login = '/login?login=viewer&password=viewer' \
- '&came_from=FrontPage&form.submitted=Login'
- viewer_wrong_login = '/login?login=viewer&password=incorrect' \
- '&came_from=FrontPage&form.submitted=Login'
- editor_login = '/login?login=editor&password=editor' \
- '&came_from=FrontPage&form.submitted=Login'
-
- def setUp(self):
- from tutorial import main
- settings = { 'sqlalchemy.url': 'sqlite://'}
- app = main({}, **settings)
- from webtest import TestApp
- self.testapp = TestApp(app)
- _initTestingDB()
-
- def tearDown(self):
- del self.testapp
- from tutorial.models import DBSession
- DBSession.remove()
-
- def test_root(self):
- res = self.testapp.get('/', status=302)
- self.assertEqual(res.location, 'http://localhost/FrontPage')
-
- def test_FrontPage(self):
- res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue(b'FrontPage' in res.body)
-
- def test_unexisting_page(self):
- self.testapp.get('/SomePage', status=404)
-
- def test_successful_log_in(self):
- res = self.testapp.get(self.viewer_login, status=302)
- self.assertEqual(res.location, 'http://localhost/FrontPage')
-
- def test_failed_log_in(self):
- res = self.testapp.get(self.viewer_wrong_login, status=200)
- self.assertTrue(b'login' in res.body)
-
- def test_logout_link_present_when_logged_in(self):
- self.testapp.get(self.viewer_login, status=302)
- res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue(b'Logout' in res.body)
-
- def test_logout_link_not_present_after_logged_out(self):
- self.testapp.get(self.viewer_login, status=302)
- self.testapp.get('/FrontPage', status=200)
- res = self.testapp.get('/logout', status=302)
- self.assertTrue(b'Logout' not in res.body)
-
- def test_anonymous_user_cannot_edit(self):
- res = self.testapp.get('/FrontPage/edit_page', status=200)
- self.assertTrue(b'Login' in res.body)
-
- def test_anonymous_user_cannot_add(self):
- res = self.testapp.get('/add_page/NewPage', status=200)
- self.assertTrue(b'Login' in res.body)
-
- def test_viewer_user_cannot_edit(self):
- self.testapp.get(self.viewer_login, status=302)
- res = self.testapp.get('/FrontPage/edit_page', status=200)
- self.assertTrue(b'Login' in res.body)
-
- def test_viewer_user_cannot_add(self):
- self.testapp.get(self.viewer_login, status=302)
- res = self.testapp.get('/add_page/NewPage', status=200)
- self.assertTrue(b'Login' in res.body)
-
- def test_editors_member_user_can_edit(self):
- self.testapp.get(self.editor_login, status=302)
- res = self.testapp.get('/FrontPage/edit_page', status=200)
- self.assertTrue(b'Editing' in res.body)
-
- def test_editors_member_user_can_add(self):
- self.testapp.get(self.editor_login, status=302)
- res = self.testapp.get('/add_page/NewPage', status=200)
- self.assertTrue(b'Editing' in res.body)
-
- def test_editors_member_user_can_view(self):
- self.testapp.get(self.editor_login, status=302)
- res = self.testapp.get('/FrontPage', status=200)
- self.assertTrue(b'FrontPage' in res.body)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/__init__.py
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/__init__.py
@@ -0,0 +1 @@
+
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
new file mode 100644
index 000000000..339c60bc2
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
@@ -0,0 +1,141 @@
+import unittest
+
+from pyramid import testing
+
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+def _register_routes(config):
+ config.add_route('view_page', '{pagename}')
+ config.add_route('edit_page', '{pagename}/edit_page')
+ config.add_route('add_page', 'add_page/{pagename}')
+
+
+class FunctionalTests(unittest.TestCase):
+
+ viewer_login = '/login?login=viewer&password=viewer' \
+ '&came_from=FrontPage&form.submitted=Login'
+ viewer_wrong_login = '/login?login=viewer&password=incorrect' \
+ '&came_from=FrontPage&form.submitted=Login'
+ editor_login = '/login?login=editor&password=editor' \
+ '&came_from=FrontPage&form.submitted=Login'
+
+ engine = None
+
+ @staticmethod
+ def setup_database():
+ import transaction
+ from tutorial.models.mymodel import Page
+ from tutorial.models.meta import (
+ Base,
+ )
+ import tutorial.models.meta
+
+
+ def initialize_db(dbsession, engine):
+ Base.metadata.create_all(engine)
+ with transaction.manager:
+ model = Page(name='FrontPage', data='This is the front page')
+ dbsession.add(model)
+
+ def wrap_get_session(transaction_manager, dbmaker):
+ dbsession = get_session(transaction_manager, dbmaker)
+ initialize_db(dbsession, engine)
+ tutorial.models.meta.get_session = get_session
+ tutorial.models.meta.get_engine = get_engine
+ return dbsession
+
+ def wrap_get_engine(settings):
+ global engine
+ engine = get_engine(settings)
+ return engine
+
+ get_session = tutorial.models.meta.get_session
+ tutorial.models.meta.get_session = wrap_get_session
+
+ get_engine = tutorial.models.meta.get_engine
+ tutorial.models.meta.get_engine = wrap_get_engine
+
+ @classmethod
+ def setUpClass(cls):
+ cls.setup_database()
+
+ from webtest import TestApp
+ from tutorial import main
+ settings = {'sqlalchemy.url': 'sqlite://'}
+ app = main({}, **settings)
+ cls.testapp = TestApp(app)
+
+ @classmethod
+ def tearDownClass(cls):
+ from tutorial.models.meta import Base
+ Base.metadata.drop_all(engine)
+
+ def tearDown(self):
+ import transaction
+ transaction.abort()
+
+ def test_root(self):
+ res = self.testapp.get('/', status=302)
+ self.assertEqual(res.location, 'http://localhost/FrontPage')
+
+ def test_FrontPage(self):
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue(b'FrontPage' in res.body)
+
+ def test_unexisting_page(self):
+ self.testapp.get('/SomePage', status=404)
+
+ def test_successful_log_in(self):
+ res = self.testapp.get(self.viewer_login, status=302)
+ self.assertEqual(res.location, 'http://localhost/FrontPage')
+
+ def test_failed_log_in(self):
+ res = self.testapp.get(self.viewer_wrong_login, status=200)
+ self.assertTrue(b'login' in res.body)
+
+ def test_logout_link_present_when_logged_in(self):
+ self.testapp.get(self.viewer_login, status=302)
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue(b'Logout' in res.body)
+
+ def test_logout_link_not_present_after_logged_out(self):
+ self.testapp.get(self.viewer_login, status=302)
+ self.testapp.get('/FrontPage', status=200)
+ res = self.testapp.get('/logout', status=302)
+ self.assertTrue(b'Logout' not in res.body)
+
+ def test_anonymous_user_cannot_edit(self):
+ res = self.testapp.get('/FrontPage/edit_page', status=200)
+ self.assertTrue(b'Login' in res.body)
+
+ def test_anonymous_user_cannot_add(self):
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue(b'Login' in res.body)
+
+ def test_viewer_user_cannot_edit(self):
+ self.testapp.get(self.viewer_login, status=302)
+ res = self.testapp.get('/FrontPage/edit_page', status=200)
+ self.assertTrue(b'Login' in res.body)
+
+ def test_viewer_user_cannot_add(self):
+ self.testapp.get(self.viewer_login, status=302)
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue(b'Login' in res.body)
+
+ def test_editors_member_user_can_edit(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/FrontPage/edit_page', status=200)
+ self.assertTrue(b'Editing' in res.body)
+
+ def test_editors_member_user_can_add(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue(b'Editing' in res.body)
+
+ def test_editors_member_user_can_view(self):
+ self.testapp.get(self.editor_login, status=302)
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue(b'FrontPage' in res.body)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py
new file mode 100644
index 000000000..d70311e38
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py
@@ -0,0 +1,168 @@
+import unittest
+import transaction
+
+from pyramid import testing
+
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+def _register_routes(config):
+ config.add_route('view_page', '{pagename}')
+ config.add_route('edit_page', '{pagename}/edit_page')
+ config.add_route('add_page', 'add_page/{pagename}')
+
+
+class BaseTest(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('..models.meta')
+ _register_routes(self.config)
+ settings = self.config.get_settings()
+
+ from ..models.meta import (
+ get_session,
+ get_engine,
+ get_dbmaker,
+ )
+
+ self.engine = get_engine(settings)
+ dbmaker = get_dbmaker(self.engine)
+
+ self.session = get_session(transaction.manager, dbmaker)
+
+ self.init_database()
+
+ def init_database(self):
+ from ..models.meta import Base
+ Base.metadata.create_all(self.engine)
+
+ def tearDown(self):
+ testing.tearDown()
+ transaction.abort()
+
+
+class ViewWikiTests(unittest.TestCase):
+
+ def setUp(self):
+ self.config = testing.setUp()
+ _register_routes(self.config)
+
+ def tearDown(self):
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views.default import view_wiki
+ return view_wiki(request)
+
+ def test_it(self):
+ request = testing.DummyRequest()
+ response = self._callFUT(request)
+ self.assertEqual(response.location, 'http://example.com/FrontPage')
+
+
+class ViewPageTests(BaseTest):
+ def setUp(self):
+ super(ViewPageTests, self).setUp()
+
+ def tearDown(self):
+ transaction.abort()
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views.default import view_page
+ return view_page(request)
+
+ def test_it(self):
+ # add a page to the db
+ from ..models.mymodel import Page
+ page = Page(name='IDoExist', data='Hello CruelWorld IDoExist')
+ self.session.add(page)
+
+ # create a request asking for the page we've created
+ request = dummy_request(self.session)
+ request.matchdict['pagename'] = 'IDoExist'
+
+ # call the view we're testing and check its behavior
+ info = self._callFUT(request)
+ self.assertEqual(info['page'], page)
+ self.assertEqual(
+ info['content'],
+ '<div class="document">\n'
+ '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
+ 'CruelWorld</a> '
+ '<a href="http://example.com/IDoExist">'
+ 'IDoExist</a>'
+ '</p>\n</div>\n')
+ self.assertEqual(info['edit_url'],
+ 'http://example.com/IDoExist/edit_page')
+
+
+class AddPageTests(BaseTest):
+ def setUp(self):
+ super(AddPageTests, self).setUp()
+
+ def tearDown(self):
+ transaction.abort()
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views.default import add_page
+ return add_page(request)
+
+ def test_it_notsubmitted(self):
+ request = dummy_request(self.session)
+ request.matchdict = {'pagename': 'AnotherPage'}
+ info = self._callFUT(request)
+ self.assertEqual(info['page'].data, '')
+ self.assertEqual(info['save_url'],
+ 'http://example.com/add_page/AnotherPage')
+
+ def test_it_submitted(self):
+ from ..models.mymodel import Page
+ request = testing.DummyRequest({'form.submitted': True,
+ 'body': 'Hello yo!'},
+ dbsession=self.session)
+ request.matchdict = {'pagename': 'AnotherPage'}
+ self._callFUT(request)
+ page = self.session.query(Page).filter_by(name='AnotherPage').one()
+ self.assertEqual(page.data, 'Hello yo!')
+
+
+class EditPageTests(BaseTest):
+ def setUp(self):
+ super(EditPageTests, self).setUp()
+
+ def tearDown(self):
+ transaction.abort()
+ testing.tearDown()
+
+ def _callFUT(self, request):
+ from tutorial.views.default import edit_page
+ return edit_page(request)
+
+ def test_it_notsubmitted(self):
+ from ..models.mymodel import Page
+ request = dummy_request(self.session)
+ request.matchdict = {'pagename': 'abc'}
+ page = Page(name='abc', data='hello')
+ self.session.add(page)
+ info = self._callFUT(request)
+ self.assertEqual(info['page'], page)
+ self.assertEqual(info['save_url'],
+ 'http://example.com/abc/edit_page')
+
+ def test_it_submitted(self):
+ from ..models.mymodel import Page
+ request = testing.DummyRequest({'form.submitted': True,
+ 'body': 'Hello yo!'},
+ dbsession=self.session)
+ request.matchdict = {'pagename': 'abc'}
+ page = Page(name='abc', data='hello')
+ self.session.add(page)
+ response = self._callFUT(request)
+ self.assertEqual(response.location, 'http://example.com/abc')
+ self.assertEqual(page.data, 'Hello yo!')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
index e954d5a31..f35f041a4 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
@@ -1,3 +1,4 @@
+import cgi
import re
from docutils.core import publish_parts
@@ -16,13 +17,9 @@ from pyramid.security import (
forget,
)
-from .security import USERS
-
-from .models import (
- DBSession,
- Page,
- )
+from ..security.default import USERS
+from ..models.mymodel import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@@ -30,26 +27,26 @@ wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@view_config(route_name='view_wiki',
permission='view')
def view_wiki(request):
- return HTTPFound(location = request.route_url('view_page',
- pagename='FrontPage'))
+ return HTTPFound(location=request.route_url('view_page',
+ pagename='FrontPage'))
-@view_config(route_name='view_page', renderer='templates/view.pt',
+@view_config(route_name='view_page', renderer='templates/view.jinja2',
permission='view')
def view_page(request):
pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).first()
+ page = request.dbsession.query(Page).filter_by(name=pagename).first()
if page is None:
return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
- exists = DBSession.query(Page).filter_by(name=word).all()
+ exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
- return '<a href="%s">%s</a>' % (view_url, 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)
@@ -57,14 +54,14 @@ def view_page(request):
return dict(page=page, content=content, edit_url=edit_url,
logged_in=request.authenticated_userid)
-@view_config(route_name='add_page', renderer='templates/edit.pt',
+@view_config(route_name='add_page', renderer='templates/edit.jinja2',
permission='edit')
def add_page(request):
pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(name=pagename, data=body)
- DBSession.add(page)
+ request.dbsession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
save_url = request.route_url('add_page', pagename=pagename)
@@ -72,24 +69,24 @@ def add_page(request):
return dict(page=page, save_url=save_url,
logged_in=request.authenticated_userid)
-@view_config(route_name='edit_page', renderer='templates/edit.pt',
+@view_config(route_name='edit_page', renderer='templates/edit.jinja2',
permission='edit')
def edit_page(request):
pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).one()
+ page = request.dbsession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
- DBSession.add(page)
+ request.dbsession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
return dict(
page=page,
- save_url=request.route_url('edit_page', pagename=pagename),
+ save_url = request.route_url('edit_page', pagename=pagename),
logged_in=request.authenticated_userid
)
-@view_config(route_name='login', renderer='templates/login.pt')
-@forbidden_view_config(renderer='templates/login.pt')
+@view_config(route_name='login', renderer='templates/login.jinja2')
+@forbidden_view_config(renderer='templates/login.jinja2')
def login(request):
login_url = request.route_url('login')
referrer = request.url
@@ -121,4 +118,3 @@ def logout(request):
headers = forget(request)
return HTTPFound(location = request.route_url('view_wiki'),
headers = headers)
-
diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini
index a9d53b296..99c4ff0fe 100644
--- a/docs/tutorials/wiki2/src/views/development.ini
+++ b/docs/tutorials/wiki2/src/views/development.ini
@@ -27,7 +27,7 @@ sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
[server:main]
use = egg:waitress#main
-host = 0.0.0.0
+host = 127.0.0.1
port = 6543
###
@@ -68,4 +68,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini
index 4684d2f7a..97acfbd7d 100644
--- a/docs/tutorials/wiki2/src/views/production.ini
+++ b/docs/tutorials/wiki2/src/views/production.ini
@@ -1,3 +1,8 @@
+###
+# app configuration
+# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
+###
+
[app:main]
use = egg:tutorial
@@ -16,7 +21,10 @@ 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, tutorial, sqlalchemy
@@ -51,6 +59,4 @@ level = NOTSET
formatter = generic
[formatter_generic]
-format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s
-
-# End logging configuration
+format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py
index 09bd63d33..d4e5a4072 100644
--- a/docs/tutorials/wiki2/src/views/setup.py
+++ b/docs/tutorials/wiki2/src/views/setup.py
@@ -10,7 +10,7 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
requires = [
'pyramid',
- 'pyramid_chameleon',
+ 'pyramid_jinja2',
'pyramid_debugtoolbar',
'pyramid_tm',
'SQLAlchemy',
diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
index 37cae1997..d28f09ca4 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py
@@ -1,20 +1,12 @@
from pyramid.config import Configurator
-from sqlalchemy import engine_from_config
-
-from .models import (
- DBSession,
- Base,
- )
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
- Base.metadata.bind = engine
config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
+ config.include('pyramid_jinja2')
+ config.include('.models.meta')
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('view_wiki', '/')
config.add_route('view_page', '/{pagename}')
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py
deleted file mode 100644
index f028c917a..000000000
--- a/docs/tutorials/wiki2/src/views/tutorial/models.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from sqlalchemy import (
- Column,
- Integer,
- Text,
- )
-
-from sqlalchemy.ext.declarative import declarative_base
-
-from sqlalchemy.orm import (
- scoped_session,
- sessionmaker,
- )
-
-from zope.sqlalchemy import ZopeTransactionExtension
-
-DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
-Base = declarative_base()
-
-
-class Page(Base):
- """ The SQLAlchemy declarative model class for a Page object. """
- __tablename__ = 'pages'
- id = Column(Integer, primary_key=True)
- name = Column(Text, unique=True)
- data = Column(Text)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
new file mode 100644
index 000000000..7b1c62867
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/__init__.py
@@ -0,0 +1,7 @@
+from sqlalchemy.orm import configure_mappers
+# import all models classes here for sqlalchemy mappers
+# to pick up
+from .mymodel import Page # flake8: noqa
+
+# run configure mappers to ensure we avoid any race conditions
+configure_mappers()
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/meta.py b/docs/tutorials/wiki2/src/views/tutorial/models/meta.py
new file mode 100644
index 000000000..80ececd8c
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/meta.py
@@ -0,0 +1,49 @@
+from sqlalchemy import engine_from_config
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+from sqlalchemy.schema import MetaData
+import zope.sqlalchemy
+
+# Recommended naming convention used by Alembic, as various different database
+# providers will autogenerate vastly different names making migrations more
+# difficult. See: http://alembic.readthedocs.org/en/latest/naming.html
+NAMING_CONVENTION = {
+ "ix": 'ix_%(column_0_label)s',
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+ "pk": "pk_%(table_name)s"
+}
+
+metadata = MetaData(naming_convention=NAMING_CONVENTION)
+Base = declarative_base(metadata=metadata)
+
+
+def includeme(config):
+ settings = config.get_settings()
+ dbmaker = get_dbmaker(get_engine(settings))
+
+ config.add_request_method(
+ lambda r: get_session(r.tm, dbmaker),
+ 'dbsession',
+ reify=True
+ )
+
+ config.include('pyramid_tm')
+
+
+def get_session(transaction_manager, dbmaker):
+ dbsession = dbmaker()
+ zope.sqlalchemy.register(dbsession,
+ transaction_manager=transaction_manager)
+ return dbsession
+
+
+def get_engine(settings, prefix='sqlalchemy.'):
+ return engine_from_config(settings, prefix)
+
+
+def get_dbmaker(engine):
+ dbmaker = sessionmaker()
+ dbmaker.configure(bind=engine)
+ return dbmaker
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models/mymodel.py b/docs/tutorials/wiki2/src/views/tutorial/models/mymodel.py
new file mode 100644
index 000000000..45571d78e
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/models/mymodel.py
@@ -0,0 +1,14 @@
+from .meta import Base
+from sqlalchemy import (
+ Column,
+ Integer,
+ Text,
+)
+
+
+class Page(Base):
+ """ The SQLAlchemy declarative model class for a Page object. """
+ __tablename__ = 'pages'
+ id = Column(Integer, primary_key=True)
+ name = Column(Text, unique=True)
+ data = Column(Integer)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py b/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py
index 23a5f13f4..4aac4a848 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py
@@ -2,36 +2,41 @@ import os
import sys
import transaction
-from sqlalchemy import engine_from_config
-
from pyramid.paster import (
get_appsettings,
setup_logging,
)
-from ..models import (
- DBSession,
- Page,
+from ..models.meta import (
Base,
+ get_session,
+ get_engine,
+ get_dbmaker,
)
+from ..models.mymodel import Page
def usage(argv):
cmd = os.path.basename(argv[0])
- print('usage: %s <config_uri>\n'
+ print('usage: %s <config_uri> [var=value]\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def main(argv=sys.argv):
- if len(argv) != 2:
+ if len(argv) < 2:
usage(argv)
config_uri = argv[1]
setup_logging(config_uri)
settings = get_appsettings(config_uri)
- engine = engine_from_config(settings, 'sqlalchemy.')
- DBSession.configure(bind=engine)
+
+ engine = get_engine(settings)
+ dbmaker = get_dbmaker(engine)
+
+ dbsession = get_session(transaction.manager, dbmaker)
+
Base.metadata.create_all(engine)
+
with transaction.manager:
model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
+ dbsession.add(model)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/edit.jinja2
index c0c1b6c20..b3aadfc2e 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/edit.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>Edit{% if page.name %} {{page.name}}{% endif %} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,26 +22,26 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
<p>
- Editing <strong><span tal:replace="page.name">Page Name Goes
- Here</span></strong>
+ Editing <strong>{% if page.name %}{{page.name}}{% else %}Page Name Goes Here{% endif %}</strong>
</p>
<p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
+ <a href="{{request.application_url}}">FrontPage</a>.
</p>
- <form action="${save_url}" method="post">
+ <form action="{{ save_url }}" method="post">
<div class="form-group">
- <textarea class="form-control" name="body" tal:content="page.data" rows="10" cols="60"></textarea>
+ <textarea class="form-control" name="body" rows="10" cols="60">{{ page.data }}</textarea>
</div>
<div class="form-group">
<button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
new file mode 100644
index 000000000..ff624c65b
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/layout.jinja2
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
+
+ <title>Alchemy Scaffold for The Pyramid Web Framework</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ {% block content %}
+ <p>No content</p>
+ {% endblock content %}
+ </div>
+ </div>
+ <div class="row">
+ <div class="links">
+ <ul>
+ <li class="current-version">Generated by v1.7.dev0</li>
+ <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/1.7-branch/">Docs</a></li>
+ <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
+ <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
+ <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
+ </ul>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.jinja2 b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.jinja2
new file mode 100644
index 000000000..bb622bf5a
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.jinja2
@@ -0,0 +1,8 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="content">
+ <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
+ <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework 1.7.dev0</span>.</p>
+</div>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt
deleted file mode 100644
index c9b0cec21..000000000
--- a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt
+++ /dev/null
@@ -1,66 +0,0 @@
-<!DOCTYPE html>
-<html lang="${request.locale_name}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
-
- <title>Alchemy Scaffold for The Pyramid Web Framework</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
-
- <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy scaffold</span></h1>
- <p class="lead">Welcome to <span class="font-normal">${project}</span>, an&nbsp;application generated&nbsp;by<br>the <span class="font-normal">Pyramid Web Framework</span>.</p>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="links">
- <ul>
- <li><i class="glyphicon glyphicon-bookmark icon-muted"></i><a href="http://docs.pylonsproject.org/projects/pyramid/en/latest/">Docs</a></li>
- <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
- <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="irc://irc.freenode.net#pyramid">IRC Channel</a></li>
- <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="http://pylonsproject.org">Pylons Project</a></li>
- </ul>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
- </body>
-</html>
diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/view.jinja2
index 0f564b16c..36bb96870 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt
+++ b/docs/tutorials/wiki2/src/views/tutorial/templates/view.jinja2
@@ -1,21 +1,20 @@
<!DOCTYPE html>
-<html lang="${request.locale_name}">
+<html lang="{{request.locale_name}}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
- <title>${page.name} - Pyramid tutorial wiki (based on
- TurboGears 20-Minute Wiki)</title>
+ <title>{{page.name}} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
- <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
@@ -23,30 +22,28 @@
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
</head>
+
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
- <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div class="content">
- <div tal:replace="structure content">
- Page text goes here.
- </div>
+ <p>{{ content|safe }}</p>
<p>
- <a tal:attributes="href edit_url" href="">
+ <a href="{{ edit_url }}">
Edit this page
</a>
</p>
<p>
- Viewing <strong><span tal:replace="page.name">
- Page Name Goes Here</span></strong>
+ Viewing <strong>{% if page.name %}{{page.name}}{% else %}Page Name Goes Here{% endif %}</strong>
</p>
<p>You can return to the
- <a href="${request.application_url}">FrontPage</a>.
+ <a href="{{request.application_url}}">FrontPage</a>.
</p>
</div>
</div>
diff --git a/docs/tutorials/wiki2/src/views/tutorial/tests.py b/docs/tutorials/wiki2/src/views/tutorial/tests.py
index 9f01d2da5..b947e3bb1 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/tests.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/tests.py
@@ -3,144 +3,63 @@ import transaction
from pyramid import testing
-def _initTestingDB():
- from sqlalchemy import create_engine
- from tutorial.models import (
- DBSession,
- Page,
- Base
- )
- engine = create_engine('sqlite://')
- Base.metadata.create_all(engine)
- DBSession.configure(bind=engine)
- with transaction.manager:
- model = Page(name='FrontPage', data='This is the front page')
- DBSession.add(model)
- return DBSession
-
-def _registerRoutes(config):
- config.add_route('view_page', '{pagename}')
- config.add_route('edit_page', '{pagename}/edit_page')
- config.add_route('add_page', 'add_page/{pagename}')
-
-class ViewWikiTests(unittest.TestCase):
+
+def dummy_request(dbsession):
+ return testing.DummyRequest(dbsession=dbsession)
+
+
+class BaseTest(unittest.TestCase):
def setUp(self):
- self.config = testing.setUp()
- self.session = _initTestingDB()
+ self.config = testing.setUp(settings={
+ 'sqlalchemy.url': 'sqlite:///:memory:'
+ })
+ self.config.include('.models.meta')
+ settings = self.config.get_settings()
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
+ from .models.meta import (
+ get_session,
+ get_engine,
+ get_dbmaker,
+ )
- def _callFUT(self, request):
- from tutorial.views import view_wiki
- return view_wiki(request)
+ self.engine = get_engine(settings)
+ dbmaker = get_dbmaker(self.engine)
- def test_it(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/FrontPage')
+ self.session = get_session(transaction.manager, dbmaker)
-class ViewPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ def init_database(self):
+ from .models.meta import Base
+ Base.metadata.create_all(self.engine)
def tearDown(self):
- self.session.remove()
- testing.tearDown()
-
- def _callFUT(self, request):
- from tutorial.views import view_page
- return view_page(request)
-
- def test_it(self):
- from tutorial.models import Page
- request = testing.DummyRequest()
- request.matchdict['pagename'] = 'IDoExist'
- page = Page(name='IDoExist', data='Hello CruelWorld IDoExist')
- self.session.add(page)
- _registerRoutes(self.config)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(
- info['content'],
- '<div class="document">\n'
- '<p>Hello <a href="http://example.com/add_page/CruelWorld">'
- 'CruelWorld</a> '
- '<a href="http://example.com/IDoExist">'
- 'IDoExist</a>'
- '</p>\n</div>\n')
- self.assertEqual(info['edit_url'],
- 'http://example.com/IDoExist/edit_page')
-
-
-class AddPageTests(unittest.TestCase):
- def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ from .models.meta import Base
- def tearDown(self):
- self.session.remove()
testing.tearDown()
+ transaction.abort()
+ Base.metadata.create_all(self.engine)
+
+
+class TestMyViewSuccessCondition(BaseTest):
- def _callFUT(self, request):
- from tutorial.views import add_page
- return add_page(request)
-
- def test_it_notsubmitted(self):
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'AnotherPage'}
- info = self._callFUT(request)
- self.assertEqual(info['page'].data,'')
- self.assertEqual(info['save_url'],
- 'http://example.com/add_page/AnotherPage')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'AnotherPage'}
- self._callFUT(request)
- page = self.session.query(Page).filter_by(name='AnotherPage').one()
- self.assertEqual(page.data, 'Hello yo!')
-
-class EditPageTests(unittest.TestCase):
def setUp(self):
- self.session = _initTestingDB()
- self.config = testing.setUp()
+ super(TestMyViewSuccessCondition, self).setUp()
+ self.init_database()
- def tearDown(self):
- self.session.remove()
- testing.tearDown()
+ from .models.mymodel import MyModel
+
+ model = MyModel(name='one', value=55)
+ self.session.add(model)
+
+ def test_passing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info['one'].name, 'one')
+ self.assertEqual(info['project'], 'tutorial')
+
+
+class TestMyViewFailureCondition(BaseTest):
- def _callFUT(self, request):
- from tutorial.views import edit_page
- return edit_page(request)
-
- def test_it_notsubmitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest()
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- info = self._callFUT(request)
- self.assertEqual(info['page'], page)
- self.assertEqual(info['save_url'],
- 'http://example.com/abc/edit_page')
-
- def test_it_submitted(self):
- from tutorial.models import Page
- _registerRoutes(self.config)
- request = testing.DummyRequest({'form.submitted':True,
- 'body':'Hello yo!'})
- request.matchdict = {'pagename':'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
- response = self._callFUT(request)
- self.assertEqual(response.location, 'http://example.com/abc')
- self.assertEqual(page.data, 'Hello yo!')
+ def test_failing_view(self):
+ from .views.default import my_view
+ info = my_view(dummy_request(self.session))
+ self.assertEqual(info.status_int, 500)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/docs/tutorials/wiki2/src/views/tutorial/views/__init__.py
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
index a3707dab5..3e5c61a72 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views/default.py
@@ -9,29 +9,26 @@ from pyramid.httpexceptions import (
from pyramid.view import view_config
-from .models import (
- DBSession,
- Page,
- )
+from ..models.mymodel import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
@view_config(route_name='view_wiki')
def view_wiki(request):
- return HTTPFound(location = request.route_url('view_page',
- pagename='FrontPage'))
+ return HTTPFound(location=request.route_url('view_page',
+ pagename='FrontPage'))
-@view_config(route_name='view_page', renderer='templates/view.pt')
+@view_config(route_name='view_page', renderer='templates/view.jinja2')
def view_page(request):
pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).first()
+ page = request.dbsession.query(Page).filter_by(name=pagename).first()
if page is None:
return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
- exists = DBSession.query(Page).filter_by(name=word).all()
+ exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
view_url = request.route_url('view_page', pagename=word)
return '<a href="%s">%s</a>' % (view_url, cgi.escape(word))
@@ -44,26 +41,26 @@ def view_page(request):
edit_url = request.route_url('edit_page', pagename=pagename)
return dict(page=page, content=content, edit_url=edit_url)
-@view_config(route_name='add_page', renderer='templates/edit.pt')
+@view_config(route_name='add_page', renderer='templates/edit.jinja2')
def add_page(request):
pagename = request.matchdict['pagename']
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(name=pagename, data=body)
- DBSession.add(page)
+ request.dbsession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
save_url = request.route_url('add_page', pagename=pagename)
page = Page(name='', data='')
return dict(page=page, save_url=save_url)
-@view_config(route_name='edit_page', renderer='templates/edit.pt')
+@view_config(route_name='edit_page', renderer='templates/edit.jinja2')
def edit_page(request):
pagename = request.matchdict['pagename']
- page = DBSession.query(Page).filter_by(name=pagename).one()
+ page = request.dbsession.query(Page).filter_by(name=pagename).one()
if 'form.submitted' in request.params:
page.data = request.params['body']
- DBSession.add(page)
+ request.dbsession.add(page)
return HTTPFound(location = request.route_url('view_page',
pagename=pagename))
return dict(
diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst
index 9db95334a..fe3fdaf2c 100644
--- a/docs/tutorials/wiki2/tests.rst
+++ b/docs/tutorials/wiki2/tests.rst
@@ -2,25 +2,33 @@
Adding Tests
============
-We will now add tests for the models and the views and a few functional tests
-in ``tests.py``. Tests ensure that an application works, and that it
-continues to work when changes are made in the future.
+We will now add tests for the models and views as well as a few functional
+tests in a new ``tests`` subpackage. Tests ensure that an application works,
+and that it continues to work when changes are made in the future.
-Test the models
-===============
+The file ``tests.py`` was generated as part of the ``alchemy`` scaffold, but it
+is a common practice to put tests into a ``tests`` subpackage, especially as
+projects grow in size and complexity. Each module in the test subpackage
+should contain tests for its corresponding module in our application. Each
+corresponding pair of modules should have the same names, except the test
+module should have the prefix ``test_``.
+
+We will move parts of ``tests.py`` into appropriate new files in the ``tests``
+subpackage, and add several new tests.
+
+Start by creating a new directory and a new empty file ``tests/__init__.py``.
-To test the model class ``Page`` we'll add a new ``PageModelTests`` class to
-our ``tests.py`` file that was generated as part of the ``alchemy`` scaffold.
Test the views
==============
-We'll modify our ``tests.py`` file, adding tests for each view function we
-added previously. As a result, we'll *delete* the ``ViewTests`` class that
-the ``alchemy`` scaffold provided, and add four other test classes:
-``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``.
-These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page``
-views.
+We'll create a new ``tests/test_views.py`` file, adding tests for each view
+function we previously added to our application. As a result, we'll *delete*
+the ``ViewTests`` class that the ``alchemy`` scaffold provided, and add four
+other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and
+``EditPageTests``. These test the ``view_wiki``, ``view_page``, ``add_page``,
+and ``edit_page`` views.
+
Functional tests
================
@@ -30,23 +38,34 @@ tested in the unit tests, like logging in, logging out, checking that
the ``viewer`` user cannot add or edit pages, but the ``editor`` user
can, and so on.
-View the results of all our edits to ``tests.py``
-=================================================
-Open the ``tutorial/tests.py`` module, and edit it such that it appears as
+View the results of all our edits to ``tests`` subpackage
+=========================================================
+
+Open ``tutorial/tests/test_views.py``, and edit it such that it appears as
follows:
-.. literalinclude:: src/tests/tutorial/tests.py
+.. literalinclude:: src/tests/tutorial/tests/test_views.py
:linenos:
:language: python
+Open ``tutorial/tests/test_functional.py``, and edit it such that it appears as
+follows:
+
+.. literalinclude:: src/tests/tutorial/tests/test_functional.py
+ :linenos:
+ :language: python
+
+
Running the tests
=================
We can run these tests by using ``setup.py test`` in the same way we did in
-:ref:`running_tests`. However, first we must edit our ``setup.py`` to
-include a dependency on WebTest, which we've used in our ``tests.py``.
-Change the ``requires`` list in ``setup.py`` to include ``WebTest``.
+:ref:`running_tests`. However, first we must edit our ``setup.py`` to include
+a dependency on `WebTest
+<http://docs.pylonsproject.org/projects/webtest/en/latest/>`_, which we've used
+in our ``tests.py``. Change the ``requires`` list in ``setup.py`` to include
+``WebTest``.
.. literalinclude:: src/tests/setup.py
:linenos:
@@ -56,12 +75,11 @@ Change the ``requires`` list in ``setup.py`` to include ``WebTest``.
After we've added a dependency on WebTest in ``setup.py``, we need to run
``setup.py develop`` to get WebTest installed into our virtualenv. Assuming
-our shell's current working directory is the "tutorial" distribution
-directory:
+our shell's current working directory is the "tutorial" distribution directory:
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/python setup.py develop
@@ -71,12 +89,11 @@ On Windows:
c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
-Once that command has completed successfully, we can run the tests
-themselves:
+Once that command has completed successfully, we can run the tests themselves:
On UNIX:
-.. code-block:: text
+.. code-block:: bash
$ $VENV/bin/python setup.py test -q
@@ -90,8 +107,10 @@ The expected result should look like the following:
.. code-block:: text
- ......................
+ ....................
----------------------------------------------------------------------
- Ran 21 tests in 2.700s
+ Ran 20 tests in 0.524s
OK
+
+ Process finished with exit code 0