From 879bb56558527e402bc8b0135ce2b40d24fe4a12 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Jun 2011 15:27:17 +0200 Subject: More small fixes made reading the rest of the docs and the tutorials. --- docs/tutorials/wiki/authorization.rst | 6 +-- docs/tutorials/wiki/definingviews.rst | 6 +-- docs/tutorials/wiki/index.rst | 4 +- docs/tutorials/wiki/installation.rst | 4 +- docs/tutorials/wiki2/authorization.rst | 6 +-- docs/tutorials/wiki2/basiclayout.rst | 6 +-- docs/tutorials/wiki2/definingmodels.rst | 4 +- docs/tutorials/wiki2/definingviews.rst | 59 +++++++++++----------- docs/tutorials/wiki2/index.rst | 4 +- docs/tutorials/wiki2/installation.rst | 4 +- .../wiki2/src/authorization/tutorial/models.py | 4 +- docs/tutorials/wiki2/src/models/tutorial/models.py | 4 +- docs/tutorials/wiki2/src/views/tutorial/models.py | 4 +- 13 files changed, 57 insertions(+), 58 deletions(-) (limited to 'docs/tutorials') 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/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..8fd9a9399 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. @@ -372,7 +371,7 @@ The expected output is something like: 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 + unrecognized .svn/entries format in reading manifest file 'tutorial.egg-info/SOURCES.txt' writing manifest file 'tutorial.egg-info/SOURCES.txt' running build_ext @@ -384,4 +383,4 @@ The expected output is something like: - + diff --git a/docs/tutorials/wiki2/index.rst b/docs/tutorials/wiki2/index.rst index 1aff949b9..b8bc9b4cb 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/src/ +`_. .. toctree:: :maxdepth: 2 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..422728383 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py @@ -26,8 +26,8 @@ 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) diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py index 797fff929..096b884d9 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models.py @@ -24,8 +24,8 @@ 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) diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py index 23b8afab8..11e91232e 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models.py @@ -23,8 +23,8 @@ 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) -- cgit v1.2.3 From 487f7e763935caf4ff9f2b6518512b9e915fe2c2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 5 Jun 2011 17:29:52 +0200 Subject: Make tests in the Wiki2 tutorial a separate chapter, as for Wiki1. Also add functional tests to the Wiki2 tutorial, similar to Wiki1. --- docs/tutorials/wiki2/definingviews.rst | 43 ---- docs/tutorials/wiki2/index.rst | 1 + .../wiki2/src/authorization/tutorial/models.py | 3 +- .../wiki2/src/authorization/tutorial/views.py | 8 +- docs/tutorials/wiki2/src/models/tutorial/models.py | 3 +- docs/tutorials/wiki2/src/tests/tutorial/tests.py | 267 +++++++++++++++++++++ docs/tutorials/wiki2/src/views/tutorial/models.py | 1 + docs/tutorials/wiki2/src/views/tutorial/views.py | 8 +- docs/tutorials/wiki2/tests.rst | 74 ++++++ 9 files changed, 357 insertions(+), 51 deletions(-) create mode 100644 docs/tutorials/wiki2/src/tests/tutorial/tests.py create mode 100644 docs/tutorials/wiki2/tests.rst (limited to 'docs/tutorials') diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index 8fd9a9399..cea376b77 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -341,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 b8bc9b4cb..d05d70f3c 100644 --- a/docs/tutorials/wiki2/index.rst +++ b/docs/tutorials/wiki2/index.rst @@ -23,6 +23,7 @@ tutorial can be browsed at definingmodels definingviews authorization + tests distributing diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py index 422728383..53c6d1122 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py @@ -34,8 +34,9 @@ def initialize_sql(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 096b884d9..ecc8d567b 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models.py @@ -32,8 +32,9 @@ def initialize_sql(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'], + '
\n' + '

Hello ' + 'CruelWorld ' + '' + 'IDoExist' + '

\n
\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 11e91232e..960c14941 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models.py @@ -31,6 +31,7 @@ def initialize_sql(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..57c1865b5 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 @@ -18,7 +18,9 @@ def view_wiki(request): def view_page(request): matchdict = request.matchdict 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) @@ -48,7 +50,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 -- cgit v1.2.3 From df75cee020e49cc4668448b83e0617b14904bfa2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 6 Jun 2011 08:30:01 +0200 Subject: Small fix in code for Wiki2 tutorial in the view chapter. --- docs/tutorials/wiki2/src/views/tutorial/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'docs/tutorials') diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py index 57c1865b5..f3d7f4a99 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/views.py +++ b/docs/tutorials/wiki2/src/views/tutorial/views.py @@ -16,7 +16,7 @@ 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=pagename).first() if page is None: @@ -34,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): -- cgit v1.2.3