summaryrefslogtreecommitdiff
path: root/docs/tutorials
diff options
context:
space:
mode:
authorCarlos de la Guardia <cguardia@yahoo.com>2011-06-07 01:54:50 -0500
committerCarlos de la Guardia <cguardia@yahoo.com>2011-06-07 01:54:50 -0500
commitfd5203000d4d051b51f804af023aefe59b832029 (patch)
treef92f1f163aed0d2c282fb674069f987fe4dfba47 /docs/tutorials
parenta5713863a80a493a1485057609578b907d04c770 (diff)
parentdf75cee020e49cc4668448b83e0617b14904bfa2 (diff)
downloadpyramid-fd5203000d4d051b51f804af023aefe59b832029.tar.gz
pyramid-fd5203000d4d051b51f804af023aefe59b832029.tar.bz2
pyramid-fd5203000d4d051b51f804af023aefe59b832029.zip
Merge remote branch 'cito/master'
Diffstat (limited to 'docs/tutorials')
-rw-r--r--docs/tutorials/wiki/authorization.rst6
-rw-r--r--docs/tutorials/wiki/definingviews.rst6
-rw-r--r--docs/tutorials/wiki/index.rst4
-rw-r--r--docs/tutorials/wiki/installation.rst4
-rw-r--r--docs/tutorials/wiki2/authorization.rst6
-rw-r--r--docs/tutorials/wiki2/basiclayout.rst6
-rw-r--r--docs/tutorials/wiki2/definingmodels.rst4
-rw-r--r--docs/tutorials/wiki2/definingviews.rst98
-rw-r--r--docs/tutorials/wiki2/index.rst5
-rw-r--r--docs/tutorials/wiki2/installation.rst4
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/models.py7
-rw-r--r--docs/tutorials/wiki2/src/authorization/tutorial/views.py8
-rw-r--r--docs/tutorials/wiki2/src/models/tutorial/models.py7
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests.py267
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/models.py5
-rw-r--r--docs/tutorials/wiki2/src/views/tutorial/views.py13
-rw-r--r--docs/tutorials/wiki2/tests.rst74
17 files changed, 414 insertions, 110 deletions
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst
index 8781325d2..358c1d5eb 100644
--- a/docs/tutorials/wiki/authorization.rst
+++ b/docs/tutorials/wiki/authorization.rst
@@ -64,7 +64,7 @@ Adding ``security.py``
~~~~~~~~~~~~~~~~~~~~~~
Add a ``security.py`` module within your package (in the same
-directory as ``__init__.py``, ``views.py``, etc) with the following
+directory as ``__init__.py``, ``views.py``, etc.) with the following
content:
.. literalinclude:: src/authorization/tutorial/security.py
@@ -172,7 +172,7 @@ into its template. We'll add something like this to each view body:
logged_in = authenticated_userid(request)
We'll then change the return value of each view that has an associated
-``renderer`` to pass the `resulting `logged_in`` value to the
+``renderer`` to pass the resulting ``logged_in`` value to the
template. For example:
.. ignore-next-block
@@ -291,7 +291,7 @@ as follows:
credentials with the username ``editor``, password ``editor`` will
show the edit page form being displayed.
-- After logging in (as a result of hitting an edit or add page and
+- After logging in (as a result of hitting an edit or add page and
submitting the login form with the ``editor`` credentials), we'll see
a Logout link in the upper right hand corner. When we click it,
we're logged out, and redirected back to the front page.
diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst
index b6c083bbf..ae4fa6ffb 100644
--- a/docs/tutorials/wiki/definingviews.rst
+++ b/docs/tutorials/wiki/definingviews.rst
@@ -23,7 +23,7 @@ assumed to return a :term:`response` object.
the request as a single argument, you can obtain it via
``request.context``.
-We're going to define several :term:`view callable` functions then wire them
+We're going to define several :term:`view callable` functions, then wire them
into :app:`Pyramid` using some :term:`view configuration`.
The source code for this tutorial stage can be browsed via
@@ -202,8 +202,8 @@ the form post view callable for the form it renders. The ``context`` of the
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
-simply renders the edit form, passing the request, the page resource, and a
-save_url which will be used as the action of the generated form.
+simply renders the edit form, passing the page resource, and a ``save_url``
+which will be used as the action of the generated form.
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), the view grabs the
diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst
index c984c4f01..3edc6ba04 100644
--- a/docs/tutorials/wiki/index.rst
+++ b/docs/tutorials/wiki/index.rst
@@ -11,8 +11,8 @@ authentication.
For cut and paste purposes, the source code for all stages of this
tutorial can be browsed at
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki>`_.
+`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/
+<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/>`_.
.. toctree::
:maxdepth: 2
diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst
index f4fb4323c..30fb67441 100644
--- a/docs/tutorials/wiki/installation.rst
+++ b/docs/tutorials/wiki/installation.rst
@@ -122,7 +122,7 @@ Preparation, Windows
.. code-block:: text
- c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 \
+ c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 ^
repoze.zodbconn nose coverage
.. _making_a_project:
@@ -234,7 +234,7 @@ On Windows:
.. code-block:: text
- c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \
+ c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^
--cover-erase --with-coverage
Looks like the code in the ``pyramid_zodb`` scaffold for ZODB projects is
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index 64c587f07..76ce4b83f 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -76,7 +76,7 @@ information about what an :term:`ACL` represents.
:meth:`pyramid.config.Configurator.add_route` for more info.
We'll pass the ``RootFactory`` we created in the step above in as the
-``root_factory`` argument to a :term:`Configurator`.
+``root_factory`` argument to a :term:`Configurator`.
Configuring an Authorization Policy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -116,7 +116,7 @@ We'll also change ``__init__.py``, adding a call to
:term:`view callable`. This is also known as a :term:`forbidden view`:
.. literalinclude:: src/authorization/tutorial/__init__.py
- :lines: 41-43
+ :lines: 25,41-43
:linenos:
:language: python
@@ -163,7 +163,7 @@ Adding ``security.py``
----------------------
Add a ``security.py`` module within your package (in the same directory as
-:file:`__init__.py`, :file:`views.py`, etc) with the following content:
+:file:`__init__.py`, :file:`views.py`, etc.) with the following content:
.. literalinclude:: src/authorization/tutorial/security.py
:linenos:
diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst
index 82e112c64..6151e0e25 100644
--- a/docs/tutorials/wiki2/basiclayout.rst
+++ b/docs/tutorials/wiki2/basiclayout.rst
@@ -92,7 +92,7 @@ use :meth:`pyramid.config.Configurator.add_view` in :term:`URL dispatch` to
register views for the routes, mapping your patterns to code:
.. literalinclude:: src/basiclayout/tutorial/__init__.py
- :lines: 14
+ :lines: 14-15
:language: py
The first positional ``add_view`` argument ``tutorial.views.my_view`` is the
@@ -102,7 +102,7 @@ which returns a response or a dictionary. This view also names a
``renderer``, which is a template which lives in the ``templates``
subdirectory of the package. When the ``tutorial.views.my_view`` view
returns a dictionary, a :term:`renderer` will use this template to create a
-response. This
+response.
Finally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app`
method to return a :term:`WSGI` application:
@@ -133,7 +133,7 @@ Let's take a look. First, we need some imports to support later code.
:linenos:
:language: py
-Next we set up a SQLAlchemy "DBSession" object:
+Next we set up a SQLAlchemy "DBSession" object:
.. literalinclude:: src/basiclayout/tutorial/models.py
:lines: 15-16
diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst
index e5d283125..7aa2214fc 100644
--- a/docs/tutorials/wiki2/definingmodels.rst
+++ b/docs/tutorials/wiki2/definingmodels.rst
@@ -45,7 +45,7 @@ SQLAlchemy models are easier to use than directly-mapped ones.
:language: python
As you can see, our ``Page`` class has a class level attribute
-``__tablename__`` which equals the string ``pages``. This means that
+``__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`` and
``data`` (all instances of :class:`sqlalchemy.Column`). These will map to
@@ -67,7 +67,7 @@ Here, we're using a slightly different binding syntax. It is otherwise
largely the same as the ``initialize_sql`` in the paster-generated
``models.py``.
-Our DBSession assignment stays the same as the original generated
+Our ``DBSession`` assignment stays the same as the original generated
``models.py``.
Looking at the Result of all Our Edits to ``models.py``
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
index 832f90b92..cea376b77 100644
--- a/docs/tutorials/wiki2/definingviews.rst
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -25,9 +25,9 @@ match has an attribute named ``matchdict`` that contains the elements placed
into the URL by the ``pattern`` of a ``route`` statement. For instance, if a
call to :meth:`pyramid.config.Configurator.add_route` in ``__init__.py`` had
the pattern ``{one}/{two}``, and the URL at ``http://example.com/foo/bar``
-was invoked, matching this pattern, the matchdict dictionary attached to the
-request passed to the view would have a ``one`` key with the value ``foo``
-and a ``two`` key with the value ``bar``.
+was invoked, matching this pattern, the ``matchdict`` dictionary attached to
+the request passed to the view would have a ``'one'`` key with the value
+``'foo'`` and a ``'two'`` key with the value ``'bar'``.
The source code for this tutorial stage can be browsed at
`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/
@@ -80,9 +80,9 @@ to be edited. We'll describe each one briefly and show the resulting
The ``view_wiki`` view function
-------------------------------
-The ``view_wiki`` function will respond as the :term:`default view` of a
-``Wiki`` model object. It always redirects to a URL which represents the
-path to our "FrontPage".
+The ``view_wiki`` function is the :term:`default view` that will be called
+when a request is made to the root URL of our wiki. It always redirects to
+a URL which represents the path to our "FrontPage".
.. literalinclude:: src/views/tutorial/views.py
:pyobject: view_wiki
@@ -99,11 +99,10 @@ page (e.g. ``http://localhost:6543/FrontPage``), and will use it as the
The ``view_page`` view function
-------------------------------
-The ``view_page`` function will respond as the :term:`default view` of
-a ``Page`` object. The ``view_page`` function renders the
-:term:`ReStructuredText` body of a page (stored as the ``data``
-attribute of a Page object) as HTML. Then it substitutes an HTML
-anchor for each *WikiWord* reference in the rendered HTML using a
+The ``view_page`` function will be used to show a single page of our
+wiki. It renders the :term:`ReStructuredText` body of a page (stored as
+the ``data`` attribute of a Page object) as HTML. Then it substitutes an
+HTML anchor for each *WikiWord* reference in the rendered HTML using a
compiled regular expression.
.. literalinclude:: src/views/tutorial/views.py
@@ -146,15 +145,15 @@ will have the values we need to construct URLs and find model objects.
:linenos:
:language: python
-The matchdict will have a ``pagename`` key that matches the name of the page
-we'd like to add. If our add view is invoked via,
-e.g. ``http://localhost:6543/add_page/SomeName``, the ``pagename`` value in
-the matchdict will be ``SomeName``.
+The ``matchdict`` will have a ``'pagename'`` key that matches the name of
+the page we'd like to add. If our add view is invoked via,
+e.g. ``http://localhost:6543/add_page/SomeName``, the value for
+``'pagename'`` in the ``matchdict`` will be ``'SomeName'``.
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
callable renders a template. To do so, it generates a "save url" which the
-template use as the form post URL during rendering. We're lazy here, so
+template uses as the form post URL during rendering. We're lazy here, so
we're trying to use the same template (``templates/edit.pt``) for the add
view as well as the page edit view, so we create a dummy Page object in order
to satisfy the edit form's desire to have *some* page object exposed as
@@ -163,10 +162,10 @@ view to a response.
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), we scrape the page body
-from the form data, create a Page object using the name in the matchdict
-``pagename``, and obtain the page body from the request, and save it into the
-database using ``session.add``. We then redirect back to the ``view_page``
-view (the :term:`default view` for a Page) for the newly created page.
+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
+``session.add``. We then redirect back to the ``view_page`` view for the
+newly created page.
The ``edit_page`` view function
-------------------------------
@@ -174,7 +173,7 @@ The ``edit_page`` view function
The ``edit_page`` function will be invoked when a user clicks the "Edit this
Page" button on the view form. It renders an edit form but it also acts as
the handler for the form it renders. The ``matchdict`` attribute of the
-request passed to the ``edit_page`` view will have a ``pagename`` key
+request passed to the ``edit_page`` view will have a ``'pagename'`` key
matching the name of the page the user wants to edit.
.. literalinclude:: src/views/tutorial/views.py
@@ -184,14 +183,14 @@ matching the name of the page the user wants to edit.
If the view execution is *not* a result of a form submission (if the
expression ``'form.submitted' in request.params`` is ``False``), the view
-simply renders the edit form, passing the request, the page object, and a
-save_url which will be used as the action of the generated form.
+simply renders the edit form, passing the page object and a ``save_url``
+which will be used as the action of the generated form.
If the view execution *is* a result of a form submission (if the expression
``'form.submitted' in request.params`` is ``True``), the view grabs the
-``body`` element of the request parameter and sets it as the ``data``
-attribute of the page object. It then redirects to the default view of the
-wiki page, which will always be the ``view_page`` view.
+``body`` element of the request parameters and sets it as the ``data``
+attribute of the page object. It then redirects to the ``view_page`` view
+of the wiki page.
Viewing the Result of all Our Edits to ``views.py``
===================================================
@@ -274,7 +273,7 @@ Mapping Views to URLs in ``__init__.py``
The ``__init__.py`` file contains
:meth:`pyramid.config.Configurator.add_view` calls which serve to map
routes via :term:`url dispatch` to views. First, we’ll get rid of the
-existing route created by the template using the name ``home``. It’s only an
+existing route created by the template using the name ``'home'``. It’s only an
example and isn’t relevant to our application.
We then need to add four calls to ``add_route``. Note that the *ordering* of
@@ -282,7 +281,7 @@ these declarations is very important. ``route`` declarations are matched in
the order they're found in the ``__init__.py`` file.
#. Add a declaration which maps the pattern ``/`` (signifying the root URL)
- to the route named ``view_wiki``.
+ to the route named ``view_wiki``.
#. Add a declaration which maps the pattern ``/{pagename}`` to the route named
``view_page``. This is the regular view for a page.
@@ -342,46 +341,3 @@ Exception('Forced Exception')``). Then visit the error-raising view
in a browser. You should see an interactive exception handler in the
browser which allows you to examine values in a post-mortem mode.
-Adding Tests
-============
-
-Since we've added a good bit of imperative code here, it's useful to
-define tests for the views we've created. We'll change our tests.py
-module to look like this:
-
-.. literalinclude:: src/views/tutorial/tests.py
- :linenos:
- :language: python
-
-We can then run the tests using something like:
-
-.. code-block:: text
- :linenos:
-
- $ python setup.py test -q
-
-The expected output is something like:
-
-.. code-block:: text
- :linenos:
-
- running test
- running egg_info
- writing requirements to tutorial.egg-info/requires.txt
- writing tutorial.egg-info/PKG-INFO
- writing top-level names to tutorial.egg-info/top_level.txt
- writing dependency_links to tutorial.egg-info/dependency_links.txt
- writing entry points to tutorial.egg-info/entry_points.txt
- unrecognized .svn/entries format in
- reading manifest file 'tutorial.egg-info/SOURCES.txt'
- writing manifest file 'tutorial.egg-info/SOURCES.txt'
- running build_ext
- ......
- ----------------------------------------------------------------------
- Ran 6 tests in 0.181s
-
- OK
-
-
-
-
diff --git a/docs/tutorials/wiki2/index.rst b/docs/tutorials/wiki2/index.rst
index 1aff949b9..d05d70f3c 100644
--- a/docs/tutorials/wiki2/index.rst
+++ b/docs/tutorials/wiki2/index.rst
@@ -11,8 +11,8 @@ basic Wiki application with authentication.
For cut and paste purposes, the source code for all stages of this
tutorial can be browsed at
-`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/
-<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/>`_.
+`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/
+<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/>`_.
.. toctree::
:maxdepth: 2
@@ -23,6 +23,7 @@ tutorial can be browsed at
definingmodels
definingviews
authorization
+ tests
distributing
diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst
index 5f5b0c216..bd597b5df 100644
--- a/docs/tutorials/wiki2/installation.rst
+++ b/docs/tutorials/wiki2/installation.rst
@@ -73,7 +73,7 @@ Preparation, Windows
.. code-block:: text
- c:\pyramidtut> Scripts\easy_install docutils \
+ c:\pyramidtut> Scripts\easy_install docutils ^
nose coverage zope.sqlalchemy SQLAlchemy repoze.tm2
@@ -205,7 +205,7 @@ On Windows:
.. code-block:: text
- c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \
+ c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^
--cover-erase --with-coverage
Looks like our package's ``models`` module doesn't quite have 100%
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py
index 487299c4c..53c6d1122 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py
@@ -26,16 +26,17 @@ class Page(Base):
data = Column(Text)
def __init__(self, name, data):
- self.name = name
- self.data = data
+ self.name = name
+ self.data = data
def initialize_sql(engine):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
Base.metadata.create_all(engine)
try:
+ transaction.begin()
session = DBSession()
- page = Page('FrontPage', 'initial data')
+ page = Page('FrontPage', 'This is the front page')
session.add(page)
transaction.commit()
except IntegrityError:
diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
index 5abd8391e..e0b84971d 100644
--- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py
@@ -2,7 +2,7 @@ import re
from docutils.core import publish_parts
-from pyramid.httpexceptions import HTTPFound
+from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.security import authenticated_userid
from pyramid.url import route_url
@@ -19,7 +19,9 @@ def view_wiki(request):
def view_page(request):
pagename = request.matchdict['pagename']
session = DBSession()
- page = session.query(Page).filter_by(name=pagename).one()
+ page = session.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
@@ -51,7 +53,7 @@ def add_page(request):
page = Page('', '')
logged_in = authenticated_userid(request)
return dict(page=page, save_url=save_url, logged_in=logged_in)
-
+
def edit_page(request):
name = request.matchdict['pagename']
session = DBSession()
diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py
index 797fff929..ecc8d567b 100644
--- a/docs/tutorials/wiki2/src/models/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/models/tutorial/models.py
@@ -24,16 +24,17 @@ class Page(Base):
data = Column(Text)
def __init__(self, name, data):
- self.name = name
- self.data = data
+ self.name = name
+ self.data = data
def initialize_sql(engine):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
Base.metadata.create_all(engine)
try:
+ transaction.begin()
session = DBSession()
- page = Page('FrontPage', 'initial data')
+ page = Page('FrontPage', 'This is the front page')
session.add(page)
transaction.commit()
except IntegrityError:
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests.py b/docs/tutorials/wiki2/src/tests/tutorial/tests.py
new file mode 100644
index 000000000..bb75efeb5
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests.py
@@ -0,0 +1,267 @@
+import unittest
+
+from pyramid import testing
+
+
+def _initTestingDB():
+ from tutorial.models import DBSession
+ from tutorial.models import Base
+ from sqlalchemy import create_engine
+ engine = create_engine('sqlite:///:memory:')
+ DBSession.configure(bind=engine)
+ Base.metadata.bind = engine
+ Base.metadata.create_all(engine)
+ 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 PageModelTests(unittest.TestCase):
+
+ def setUp(self):
+ self.session = _initTestingDB()
+
+ def tearDown(self):
+ self.session.remove()
+
+ def _getTargetClass(self):
+ from tutorial.models import Page
+ return Page
+
+ def _makeOne(self, name='SomeName', data='some data'):
+ return self._getTargetClass()(name, data)
+
+ def test_constructor(self):
+ instance = self._makeOne()
+ self.assertEqual(instance.name, 'SomeName')
+ self.assertEqual(instance.data, 'some data')
+
+class InitializeSqlTests(unittest.TestCase):
+
+ def setUp(self):
+ from tutorial.models import DBSession
+ DBSession.remove()
+
+ def tearDown(self):
+ from tutorial.models import DBSession
+ DBSession.remove()
+
+ def _callFUT(self, engine):
+ from tutorial.models import initialize_sql
+ return initialize_sql(engine)
+
+ def test_it(self):
+ from sqlalchemy import create_engine
+ engine = create_engine('sqlite:///:memory:')
+ self._callFUT(engine)
+ from tutorial.models import DBSession, Page
+ self.assertEqual(DBSession.query(Page).one().data,
+ 'This is the front page')
+
+class ViewWikiTests(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp()
+
+ 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('IDoExist', '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()
+ self.config.begin()
+
+ 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('abc', '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('abc', '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:///:memory:'}
+ app = main({}, **settings)
+ from webtest import TestApp
+ self.testapp = TestApp(app)
+
+ def tearDown(self):
+ del self.testapp
+ from tutorial.models import DBSession
+ DBSession.remove()
+
+ def test_root(self):
+ res = self.testapp.get('/', status=302)
+ self.assertTrue(not res.body)
+
+ def test_FrontPage(self):
+ res = self.testapp.get('/FrontPage', status=200)
+ self.assertTrue('FrontPage' in res.body)
+
+ def test_unexisting_page(self):
+ res = self.testapp.get('/SomePage', status=404)
+ self.assertTrue('No such page' in res.body, res.body)
+
+ def test_successful_log_in(self):
+ res = self.testapp.get(self.viewer_login, status=302)
+ self.assertTrue(res.location == 'FrontPage')
+
+ def test_failed_log_in(self):
+ res = self.testapp.get(self.viewer_wrong_login, status=200)
+ self.assertTrue('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('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('Logout' not in res.body)
+
+ def test_anonymous_user_cannot_edit(self):
+ res = self.testapp.get('/FrontPage/edit_page', status=200)
+ self.assertTrue('Login' in res.body)
+
+ def test_anonymous_user_cannot_add(self):
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue('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('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('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('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('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('FrontPage' in res.body)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py
index 23b8afab8..960c14941 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/models.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/models.py
@@ -23,14 +23,15 @@ class Page(Base):
data = Column(Text)
def __init__(self, name, data):
- self.name = name
- self.data = data
+ self.name = name
+ self.data = data
def initialize_sql(engine):
DBSession.configure(bind=engine)
Base.metadata.bind = engine
Base.metadata.create_all(engine)
try:
+ transaction.begin()
session = DBSession()
page = Page('FrontPage', 'initial data')
session.add(page)
diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py
index b8896abe7..f3d7f4a99 100644
--- a/docs/tutorials/wiki2/src/views/tutorial/views.py
+++ b/docs/tutorials/wiki2/src/views/tutorial/views.py
@@ -2,7 +2,7 @@ import re
from docutils.core import publish_parts
-from pyramid.httpexceptions import HTTPFound
+from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.url import route_url
from tutorial.models import DBSession
@@ -16,9 +16,11 @@ def view_wiki(request):
pagename='FrontPage'))
def view_page(request):
- matchdict = request.matchdict
+ pagename = request.matchdict['pagename']
session = DBSession()
- page = session.query(Page).filter_by(name=matchdict['pagename']).one()
+ page = session.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ return HTTPNotFound('No such page')
def check(match):
word = match.group(1)
@@ -32,8 +34,7 @@ def view_page(request):
content = publish_parts(page.data, writer_name='html')['html_body']
content = wikiwords.sub(check, content)
- edit_url = route_url('edit_page', request,
- pagename=matchdict['pagename'])
+ edit_url = route_url('edit_page', request, pagename=pagename)
return dict(page=page, content=content, edit_url=edit_url)
def add_page(request):
@@ -48,7 +49,7 @@ def add_page(request):
save_url = route_url('add_page', request, pagename=name)
page = Page('', '')
return dict(page=page, save_url=save_url)
-
+
def edit_page(request):
name = request.matchdict['pagename']
session = DBSession()
diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst
new file mode 100644
index 000000000..7a4e65529
--- /dev/null
+++ b/docs/tutorials/wiki2/tests.rst
@@ -0,0 +1,74 @@
+============
+Adding Tests
+============
+
+We will now add tests for the models and the views and a few functional
+tests in the ``tests.py``. Tests ensure that an application works, and
+that it continues to work after some changes are made in the future.
+
+Testing the Models
+==================
+
+We write a test class for the model class ``Page`` and another test class
+for the ``initialize_sql`` function.
+
+To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a
+result of the ``pyramid_routesalchemy`` project generator. We'll add two
+test classes: one for the ``Page`` model named ``PageModelTests``, one for the
+``initialize_sql`` function named ``InitializeSqlTests``.
+
+Testing the Views
+=================
+
+We'll modify our ``tests.py`` file, adding tests for each view function we
+added above. As a result, we'll *delete* the ``ViewTests`` test in the file,
+and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``,
+``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``,
+``view_page``, ``add_page``, and ``edit_page`` views respectively.
+
+Functional tests
+================
+
+We test the whole application, covering security aspects that are not
+tested in the unit tests, like logging in, logging out, checking that
+the ``viewer`` user cannot add or edit pages, but the ``editor`` user
+can, and so on.
+
+Viewing the results of all our edits to ``tests.py``
+====================================================
+
+Once we're done with the ``tests.py`` module, it will look a lot like the
+below:
+
+.. literalinclude:: src/tests/tutorial/tests.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`. Assuming our shell's current working directory is the
+"tutorial" distribution directory:
+
+On UNIX:
+
+.. code-block:: text
+
+ $ ../bin/python setup.py test -q
+
+On Windows:
+
+.. code-block:: text
+
+ c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q
+
+The expected result looks something like:
+
+.. code-block:: text
+
+ ......................
+ ----------------------------------------------------------------------
+ Ran 22 tests in 2.700s
+
+ OK