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.rst180
-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
95 files changed, 1807 insertions, 1487 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 e9f361e7d..8e3bb4c13 100644
--- a/docs/tutorials/wiki2/design.rst
+++ b/docs/tutorials/wiki2/design.rst
@@ -1,10 +1,9 @@
-==========
+======
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/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