diff options
| author | Chris McDonough <chrism@plope.com> | 2011-11-27 04:08:20 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-11-27 04:08:20 -0500 |
| commit | 5edd54f05b05330fa6e899a1bb1650cc7a2df33c (patch) | |
| tree | dfbeed48fc684717b3f6a5ed7a28fd5fbb1e3479 /docs | |
| parent | 596495de4aa1ab0f3a3752d21c14ac08631e8457 (diff) | |
| download | pyramid-5edd54f05b05330fa6e899a1bb1650cc7a2df33c.tar.gz pyramid-5edd54f05b05330fa6e899a1bb1650cc7a2df33c.tar.bz2 pyramid-5edd54f05b05330fa6e899a1bb1650cc7a2df33c.zip | |
- The SQLAlchemy Wiki tutorial has been updated. It now uses
``@view_config`` decorators and an explicit database population script.
Closes #359.
Diffstat (limited to 'docs')
65 files changed, 819 insertions, 600 deletions
diff --git a/docs/_themes b/docs/_themes -Subproject 03e5e5aaaeddc4c9aea887478c7e7b379a127b6 +Subproject d0c25c1398f7c975db6fc174ef957764242ccb8 diff --git a/docs/glossary.rst b/docs/glossary.rst index c98637698..39933cf4c 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -936,3 +936,8 @@ Glossary email. See its `documentation <https://docs.pylonsproject.org/projects/pyramid_exclog/dev/>`_. + console script + A script written to the ``bin`` (on UNIX, or ``Scripts`` on Windows) + directory of a Python installation or virtualenv as the result of + running ``setup.py install`` or ``setup.py develop``. + diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index df5e228fd..ab04ea405 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -4,27 +4,22 @@ Adding Authorization ==================== -Our application currently allows anyone with access to the server to -view, edit, and add pages to our wiki. For purposes of demonstration -we'll change our application to allow only people whom possess a -specific username (`editor`) to add and edit wiki pages but we'll -continue allowing anyone with access to the server to view pages. -:app:`Pyramid` provides facilities for :term:`authorization` and -:term:`authentication`. We'll make use of both features to provide security -to our application. - -We will add an :term:`authentication policy` and an -:term:`authorization policy` to our :term:`application -registry`, add a ``security.py`` module, create a :term:`root factory` -with an :term:`ACL`, and add :term:`permission` declarations to -the ``edit_page`` and ``add_page`` views. - -Then we will add ``login`` and ``logout`` views, and modify the -existing views to make them return a ``logged_in`` flag to the -renderer. - -Finally, we will add a ``login.pt`` template and change the existing -``view.pt`` and ``edit.pt`` to show a "Logout" link when not logged in. +:app:`Pyramid` provides facilities for :term:`authentication` and +:term:`authorization`. We'll make use of both features to provide security +to our application. Our application currently allows anyone with access to +the server to view, edit, and add pages to our wiki. We'll change our +application to allow only people whom possess a specific username (`editor`) +to add and edit wiki pages but we'll continue allowing anyone with access to +the server to view pages. + +To do so, we'll add an :term:`authentication policy` and an +:term:`authorization policy`. We'll also add a ``security.py`` module, +create a :term:`root factory` with an :term:`ACL`, and add :term:`permission` +declarations to the ``edit_page`` and ``add_page`` views. Then we'll add +``login`` and ``logout`` views, and modify the existing views to make them +return a ``logged_in`` flag to the renderer. Finally, we will add a +``login.pt`` template and change the existing ``view.pt`` and ``edit.pt`` to +show a "Logout" link when not logged in. The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/authorization/ @@ -54,7 +49,7 @@ inside our ``models.py`` file. Add the following statements to your ``models.py`` file: .. literalinclude:: src/authorization/tutorial/models.py - :lines: 3-4,45-50 + :lines: 1-4,35-39 :linenos: :language: python @@ -92,14 +87,14 @@ We'll change our ``__init__.py`` file to enable an declarative security checking. We need to import the new policies: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 2-3,8 + :lines: 2-3,7 :linenos: :language: python Then, we'll add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 15-21 + :lines: 16-22 :linenos: :language: python @@ -111,49 +106,12 @@ represented by this policy: it is required. The ``callback`` is a ``groupfinder`` function in the current directory's ``security.py`` file. We haven't added that module yet, but we're about to. -We'll also change ``__init__.py``, adding a call to -:meth:`pyramid.config.Configurator.add_view` that points at our ``login`` -:term:`view callable`. This is also known as a :term:`forbidden view`: - -.. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 25,41-43 - :linenos: - :language: python - -A forbidden view configures our newly created login view to show up when -:app:`Pyramid` detects that a view invocation can not be authorized. - -A ``logout`` :term:`view callable` will allow users to log out later: - -.. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 26,34 - :linenos: - :language: python - -We'll also add ``permission`` arguments with the value ``edit`` to the -``edit_page`` and ``add_page`` views. This indicates that the view -callables which these views reference cannot be invoked without the -authenticated user possessing the ``edit`` permission with respect to the -current context. - -.. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 37-40 - :linenos: - :language: python - -Adding these ``permission`` arguments causes Pyramid to make the -assertion that only users who possess the effective ``edit`` permission at -the time of the request may invoke those two views. We've granted the -``group:editors`` principal the ``edit`` permission at the root model via its -ACL, so only the a user whom is a member of the group named ``group:editors`` -will able to invoke the views associated with the ``add_page`` or -``edit_page`` routes. - Viewing Your Changes ~~~~~~~~~~~~~~~~~~~~ -When we're done configuring a root factory, adding an authorization policy, -and adding views, your application's ``__init__.py`` will look like this: +When we're done configuring a root factory, adding a authentication and +authorization policies, and adding routes for ``/login`` and ``/logout``, +your application's ``__init__.py`` will look like this: .. literalinclude:: src/authorization/tutorial/__init__.py :linenos: @@ -191,30 +149,54 @@ views, the ``editor`` user should be able to add and edit pages. Adding Login and Logout Views ----------------------------- -We'll add a ``login`` view callable which renders a login form and -processes the post from the login form, checking credentials. +To our ``views.py`` we'll add a ``login`` view callable which renders a login +form and processes the post from the login form, checking credentials. 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. -We'll add a different file (for presentation convenience) to add login -and the logout view callables. Add a file named ``login.py`` to your -application (in the same directory as ``views.py``) with the following -content: +The ``login`` view callable will look something like this: -.. literalinclude:: src/authorization/tutorial/login.py +.. literalinclude:: src/authorization/tutorial/views.py + :pyobject: login :linenos: :language: python +The ``logout`` view callable will look something like this: + +.. literalinclude:: src/authorization/tutorial/views.py + :pyobject: logout + :linenos: + :language: python + +The ``login`` view callable is decorated with two ``@view_config`` +decorators, one which associates it with the ``login`` route, the other which +associates it with the ``HTTPForbidden`` context. The one which associates +it with the ``login`` route makes it visible when we visit ``/login``. The +one which associates it with the ``HTTPForbidden`` context makes it the +:term:`forbidden view`. The forbidden view is displayed whenever Pyramid or +your application raises an HTTPForbidden exception. In this case, we'll be +relying on the forbidden view to show the login form whenver someone attempts +to execute an action which they're not yet authorized to perform. + +The ``logout`` view callable is decorated with a ``@view_config`` decorator +which associates it with the ``logout`` route. This makes it visible when we +visit ``/login``. + +We'll need to import some stuff to service the needs of these two functions: +the ``HTTPForbidden`` exception, a number of values from the +``pyramid.security`` module, and a value from our newly added +``tutorial.security`` package. + Changing Existing Views ----------------------- Then we need to change each of our ``view_page``, ``edit_page`` and -``add_page`` views in ``views.py`` to pass a "logged in" parameter to its -template. We'll add something like this to each view body: +``add_page`` view callables in ``views.py``. Within each of these views, +we'll need to pass a "logged in" parameter to its template. We'll add +something like this to each view body: -.. ignore-next-block .. code-block:: python :linenos: @@ -224,7 +206,6 @@ template. We'll add something like this to each view body: We'll then change the return value of these views to pass the `resulting `logged_in`` value to the template, e.g.: -.. ignore-next-block .. code-block:: python :linenos: @@ -233,6 +214,28 @@ We'll then change the return value of these views to pass the `resulting logged_in = logged_in, edit_url = edit_url) +We'll also need to add a ``permission`` value to the ``@view_config`` +decorator for each of the ``add_page`` and ``edit_page`` view callables. For +each, we'll add ``permission='edit'``, for example: + +.. code-block:: python + :linenos: + + @view_config(route_name='edit_page', renderer='templates/edit.pt', + permission='edit') + +See the ``permission='edit'`` we added there? This indicates that the view +callables which these views reference cannot be invoked without the +authenticated user possessing the ``edit`` permission with respect to the +current :term:`context`. + +Adding these ``permission`` arguments causes Pyramid to make the assertion +that only users who possess the effective ``edit`` permission at the time of +the request may invoke those two views. We've granted the ``group:editors`` +principal the ``edit`` permission at the root model via its ACL, so only the +a user whom is a member of the group named ``group:editors`` will able to +invoke the views associated with the ``add_page`` or ``edit_page`` routes. + Adding the ``login.pt`` Template -------------------------------- diff --git a/docs/tutorials/wiki2/basiclayout.rst b/docs/tutorials/wiki2/basiclayout.rst index dc5664c5b..77658970d 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -128,19 +128,20 @@ pattern matches is done by registering a :term:`view configuration`. Our application uses the :meth:`pyramid.view.view_config` decorator to map view callables to each route, thereby mapping URL patterns to code. -Here is the code in the ``views.py`` file within our package: +Here is the entirety of code in the ``views.py`` file within our package: .. literalinclude:: src/basiclayout/tutorial/views.py :linenos: :language: py -The important part to point out here is the ``@view_config`` decorator. In -fact, ``@view_config`` is so important that we're going to ignore the rest of -the code in the module at this point just to explain it. The -``@view_config`` decorator associates the function it decorates with a -:term:`view configuration`. The view configuration names a ``route_name`` -(``home``), and names a ``renderer``, which is a template which lives in the -``templates`` subdirectory of the package. +The important part to point out here is the ``@view_config`` decorator which +sits atop the ``my_view`` function. In fact, ``@view_config`` is so +important that we're going to ignore the rest of the code in the module at +this point just to explain it. The ``@view_config`` decorator associates the +function it decorates with a :term:`view configuration`. The view +configuration names a ``route_name`` (``home``), and names a ``renderer``, +which is a template which lives in the ``templates`` subdirectory of the +package. As the result of this view configuration, when the pattern associated with the view named ``home`` is matched during a request, the function named @@ -164,9 +165,14 @@ Content Models with ``models.py`` --------------------------------- In a SQLAlchemy-based application, a *model* object is an object composed by -querying the SQL database. SQLAlchemy is an "object relational mapper" (an -ORM). The ``models.py`` file is where the ``alchemy`` scaffold put the -classes that implement our models. +querying the SQL database. The ``models.py`` file is where the ``alchemy`` +scaffold put the classes that implement our models. + +Here is the complete source for ``models.py``: + + .. literalinclude:: src/basiclayout/tutorial/models.py + :linenos: + :language: py Let's take a look. First, we need some imports to support later code. @@ -178,7 +184,7 @@ Let's take a look. First, we need some imports to support later code. Next we set up a SQLAlchemy "DBSession" object: .. literalinclude:: src/basiclayout/tutorial/models.py - :lines: 15-16 + :lines: 16 :linenos: :language: py @@ -202,30 +208,6 @@ within the ``__init__`` function itself. The ``MyModel`` class also has a ``__tablename__`` attribute. This informs SQLAlchemy which table to use to store the data representing instances of this class. -Next we define a function named ``populate`` which adds a single -model instance into our SQL storage and commits a transaction: - - .. literalinclude:: src/basiclayout/tutorial/models.py - :pyobject: populate - :linenos: - :language: py - -The function doesn't do a lot in this case, but it's there to illustrate -how an application requiring many objects to be set up could work. - -Lastly we have a function named ``initialize_sql`` which receives a SQL -database engine and binds it to our SQLAlchemy DBSession object. It also -calls the ``populate`` function, to do initial database population. This -is the initialization function that is called from __init__.py above. - - .. literalinclude:: src/basiclayout/tutorial/models.py - :pyobject: initialize_sql - :linenos: - :language: py - -Here is the complete source for ``models.py``: - - .. literalinclude:: src/basiclayout/tutorial/models.py - :linenos: - :language: py +That's about all there is to it to models, views, and initialization code in +our stock application. diff --git a/docs/tutorials/wiki2/definingmodels.rst b/docs/tutorials/wiki2/definingmodels.rst index bc2c2de5f..cd295e993 100644 --- a/docs/tutorials/wiki2/definingmodels.rst +++ b/docs/tutorials/wiki2/definingmodels.rst @@ -15,29 +15,25 @@ Making Edits to ``models.py`` .. note:: - There is nothing automagically special about the filename - ``models.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. + There is nothing automagically special about the filename ``models.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. -The first thing we want to do 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. - -Next, we'll remove the :class:`sqlalchemy.Unicode` import and replace it -with :class:`sqlalchemy.Text`. +Here's what our ``models.py`` file should look like after this step: .. literalinclude:: src/models/tutorial/models.py - :lines: 5 :linenos: :language: py -Then, we'll add a ``Page`` class. Because this is a SQLAlchemy -application, this class should inherit from an instance of -:class:`sqlalchemy.ext.declarative.declarative_base`. Declarative -SQLAlchemy models are easier to use than directly-mapped ones. +The first thing we've done is to do 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, +this class inherits from an instance of +:class:`sqlalchemy.ext.declarative.declarative_base`. .. literalinclude:: src/models/tutorial/models.py :pyobject: Page @@ -54,24 +50,18 @@ 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. -We'll also remove our ``populate`` function. We'll inline the populate step -into ``initialize_sql``, changing our ``initialize_sql`` function to add a -FrontPage object to our database at startup time. - -.. literalinclude:: src/models/tutorial/models.py - :pyobject: initialize_sql - :linenos: - :language: python - -Here, we're using a slightly different binding syntax. It is otherwise -largely the same as the ``initialize_sql`` in the pcreate-generated -``models.py``. +Changing ``scripts/populate.py`` +-------------------------------- -Our ``DBSession`` assignment stays the same as the original generated -``models.py``. +We haven't looked at the guts of this file yet, but within the ``scripts`` +directory of your ``tutorial`` package is a file named ``populate.py``. Code +in this file is executed whenever we run the ``populate_tutorial`` command +(as we did in the installation step of this tutorial). -Looking at the Result of all Our Edits to ``models.py`` -------------------------------------------------------- +Since we've changed our model, we need to make changes to our ``populate.py`` +script. In particular, we'll replace our import of ``MyModel`` with one of +``Page`` and we'll change the very end of the script to create a ``Page`` +rather than a ``MyModel`` and add it to our ``DBSession``. The result of all of our edits to ``models.py`` will end up looking something like this: @@ -80,10 +70,52 @@ something like this: :linenos: :language: python -Populating the Database ------------------------ +Repopulating the Database +------------------------- + +Because our model has changed, in order to repopulate the database, we need +to rerun the ``populate_tutorial`` command to pick up the changes you've made +to both the models.py file and to the populate.py file. From the root of the +``tutorial`` project, directory execute the following commands. + +On UNIX: + +.. code-block:: text + + $ ../bin/populate_tutorial development.ini + +On Windows: + +.. code-block:: text -XXX The ``populate_tutorial`` :term:`console script``. + c:\pyramidtut\tutorial> ..\Scripts\populate_tutorial development.ini + +Success will look something like this:: + + 2011-11-27 01:22:45,277 INFO [sqlalchemy.engine.base.Engine][MainThread] + PRAGMA table_info("pages") + 2011-11-27 01:22:45,277 INFO [sqlalchemy.engine.base.Engine][MainThread] () + 2011-11-27 01:22:45,277 INFO [sqlalchemy.engine.base.Engine][MainThread] + CREATE TABLE pages ( + id INTEGER NOT NULL, + name TEXT, + data TEXT, + PRIMARY KEY (id), + UNIQUE (name) + ) + + + 2011-11-27 01:22:45,278 INFO [sqlalchemy.engine.base.Engine][MainThread] () + 2011-11-27 01:22:45,397 INFO [sqlalchemy.engine.base.Engine][MainThread] + COMMIT + 2011-11-27 01:22:45,400 INFO [sqlalchemy.engine.base.Engine][MainThread] + BEGIN (implicit) + 2011-11-27 01:22:45,401 INFO [sqlalchemy.engine.base.Engine][MainThread] + INSERT INTO pages (name, data) VALUES (?, ?) + 2011-11-27 01:22:45,401 INFO [sqlalchemy.engine.base.Engine][MainThread] + ('FrontPage', 'This is the front page') + 2011-11-27 01:22:45,402 INFO [sqlalchemy.engine.base.Engine][MainThread] + COMMIT Viewing the Application in a Browser ------------------------------------ diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index 21b97f7aa..7f533b635 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -2,34 +2,19 @@ Defining Views ============== -A :term:`view callable` in a :term:`url dispatch` -based :app:`Pyramid` -application is typically a simple Python function that accepts a single -parameter named :term:`request`. A view callable is assumed to return a -:term:`response` object. - -.. note:: - - A :app:`Pyramid` view can also be defined as callable - which accepts *two* arguments: a :term:`context` and a - :term:`request`. You'll see this two-argument pattern used in - other :app:`Pyramid` tutorials and applications. Either calling - convention will work in any :app:`Pyramid` application; the - calling conventions can be used interchangeably as necessary. In - :term:`url dispatch` based applications, however, the context - object is rarely used in the view body itself, so within this - tutorial we define views as callables that accept only a request to - avoid the visual "noise". If you do need the ``context`` within a - view function that only takes the request as a single argument, you - can obtain it via ``request.context``. - -The request passed to every view that is called as the result of a route -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'``. +A :term:`view callable` in a :app:`Pyramid` application is typically a simple +Python function that accepts a single parameter named :term:`request`. A +view callable is assumed to return a :term:`response` object. + +The request object passed to every view that is called as the result of a +route 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'``. The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/ @@ -52,24 +37,56 @@ Our resulting ``setup.py`` should look like so: :linenos: :language: python -.. note:: After these new dependencies are added, you will need to - rerun ``python setup.py develop`` inside the root of the - ``tutorial`` package to obtain and register the newly added - dependency package. +Running ``setup.py develop`` +============================ + +Since a new software dependency was added, you will need to rerun ``python +setup.py develop`` inside the root of the ``tutorial`` package to obtain and +register the newly added dependency distribution. + +Make sure your current working directory is the root of the project (the +directory in which setup.py lives) and execute the following command. + +On UNIX: + +.. code-block:: text + + $ cd tutorial + $ ../bin/python setup.py develop -Adding View Functions -===================== +On Windows: -We'll get rid of our ``my_view`` view function in our ``views.py`` file. -It's only an example and isn't relevant to our application. +.. code-block:: text -Then we're going to add four :term:`view callable` functions to our -``views.py`` module. One view callable (named ``view_wiki``) will display -the wiki itself (it will answer on the root URL), another named ``view_page`` -will display an individual page, another named ``add_page`` will allow a page -to be added, and a final view callable named ``edit_page`` will allow a page -to be edited. We'll describe each one briefly and show the resulting -``views.py`` file afterward. + c:\pyramidtut> cd tutorial + c:\pyramidtut\tutorial> ..\Scripts\python setup.py develop + +Success executing this command will end with a line to the console something +like:: + + Finished processing dependencies for tutorial==0.0 + +Changing the ``views.py`` File +============================== + +We're going to edit our ``views.py`` in a rather major way. The result of +all of our edits to ``views.py`` will leave it looking like this: + +.. literalinclude:: src/views/tutorial/views.py + :linenos: + :language: python + +We've gotten 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. + +Then we added four :term:`view callable` functions to our ``views.py`` +module. One view callable (named ``view_wiki``) will display the wiki itself +(it will answer on the root URL), another named ``view_page`` will display an +individual page, another named ``add_page`` will allow a page to be added, +and a final view callable named ``edit_page`` will allow a page to be edited. +We'll describe each one briefly and show the resulting ``views.py`` file +afterward. .. note:: @@ -195,16 +212,6 @@ If the view execution *is* a result of a form submission (if the expression 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`` -=================================================== - -The result of all of our edits to ``views.py`` will leave it looking -like this: - -.. literalinclude:: src/views/tutorial/views.py - :linenos: - :language: python - Adding Templates ================ @@ -270,47 +277,41 @@ 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. -Mapping Views to URLs in ``__init__.py`` -======================================== +Adding Routes to ``__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 -example and isn’t relevant to our application. +:meth:`pyramid.config.Configurator.add_route` calls which serve to add routes +to our application. First, we’ll get rid of the 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 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``. It maps to our ``view_wiki`` view + callable by virtue of the ``@view_config`` attached to the ``view_wiki`` + view function indicating ``route_name='view_wiki'``. #. Add a declaration which maps the pattern ``/{pagename}`` to the route named - ``view_page``. This is the regular view for a page. + ``view_page``. This is the regular view for a page. It maps + to our ``view_page`` view callable by virtue of the ``@view_config`` + attached to the ``view_page`` view function indicating + ``route_name='view_page'``. #. Add a declaration which maps the pattern ``/add_page/{pagename}`` to the - route named ``add_page``. This is the add view for a new page. + route named ``add_page``. This is the add view for a new page. It maps + to our ``add_page`` view callable by virtue of the ``@view_config`` + attached to the ``add_page`` view function indicating + ``route_name='add_page'``. #. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the - route named ``edit_page``. This is the edit view for a page. - -After we've defined the routes for our application, we can register views -to handle the processing and rendering that needs to happen when each route is -requested. - -#. Add a declaration which maps the ``view_wiki`` route to the view named - ``view_wiki`` in our ``views.py`` file. This is the :term:`default view` - for the wiki. - -#. Add a declaration which maps the ``view_page`` route to the view named - ``view_page`` in our ``views.py`` file. - -#. Add a declaration which maps the ``add_page`` route to the view named - ``add_page`` in our ``views.py`` file. - -#. Add a declaration which maps the ``edit_page`` route to the view named - ``edit_page`` in our ``views.py`` file. + route named ``edit_page``. This is the edit view for a page. It maps + to our ``edit_page`` view callable by virtue of the ``@view_config`` + attached to the ``edit_page`` view function indicating + ``route_name='edit_page'``. As a result of our edits, the ``__init__.py`` file should look something like so: diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 381e0a90f..be97f1cb3 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -253,7 +253,7 @@ You will see an error page with a title something like this:: Oh no! Something isn't working! -This happens because we haven't populated the database with any table +This happens because we haven't populated the SQL database with any table information yet. We need to use the ``populate_tutorial`` :term:`console script` to populate our database before we can see the page render correctly. diff --git a/docs/tutorials/wiki2/src/authorization/README.txt b/docs/tutorials/wiki2/src/authorization/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/authorization/README.txt +++ b/docs/tutorials/wiki2/src/authorization/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/authorization/production.ini +++ b/docs/tutorials/wiki2/src/authorization/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/authorization/setup.py b/docs/tutorials/wiki2/src/authorization/setup.py index 439a86923..09769bfff 100644 --- a/docs/tutorials/wiki2/src/authorization/setup.py +++ b/docs/tutorials/wiki2/src/authorization/setup.py @@ -42,6 +42,7 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) - diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py index cca52fdfe..04dd5fe82 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py @@ -4,14 +4,15 @@ from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql from tutorial.security import groupfinder +from .models import DBSession + def main(global_config, **settings): - """ This function returns a WSGI application. + """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder) authz_policy = ACLAuthorizationPolicy() @@ -19,27 +20,13 @@ def main(global_config, **settings): root_factory='tutorial.models.RootFactory', authentication_policy=authn_policy, authorization_policy=authz_policy) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('login', '/login') config.add_route('logout', '/logout') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') - - config.add_view('tutorial.views.view_wiki', route_name='view_wiki') - config.add_view('tutorial.login.login', route_name='login', - renderer='tutorial:templates/login.pt') - config.add_view('tutorial.login.logout', route_name='logout') - config.add_view('tutorial.views.view_page', route_name='view_page', - renderer='tutorial:templates/view.pt') - config.add_view('tutorial.views.add_page', route_name='add_page', - renderer='tutorial:templates/edit.pt', permission='edit') - config.add_view('tutorial.views.edit_page', route_name='edit_page', - renderer='tutorial:templates/edit.pt', permission='edit') - config.add_view('tutorial.login.login', - context='pyramid.httpexceptions.HTTPForbidden', - renderer='tutorial:templates/login.pt') + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/login.py b/docs/tutorials/wiki2/src/authorization/tutorial/login.py deleted file mode 100644 index 5a825d8d6..000000000 --- a/docs/tutorials/wiki2/src/authorization/tutorial/login.py +++ /dev/null @@ -1,37 +0,0 @@ -from pyramid.httpexceptions import HTTPFound -from pyramid.security import remember -from pyramid.security import forget - -from tutorial.security import USERS - -def login(request): - login_url = request.route_url('login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use the login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' - login = '' - password = '' - if 'form.submitted' in request.params: - login = request.params['login'] - password = request.params['password'] - if USERS.get(login) == password: - headers = remember(request, login) - return HTTPFound(location = came_from, - headers = headers) - message = 'Failed login' - - return dict( - message = message, - url = request.application_url + '/login', - came_from = came_from, - login = login, - password = password, - ) - -def logout(request): - headers = forget(request) - return HTTPFound(location = request.route_url('view_wiki'), - headers = headers) - diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/models.py b/docs/tutorials/wiki2/src/authorization/tutorial/models.py index 832545cb1..c3bdcbea5 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/models.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/models.py @@ -1,17 +1,20 @@ -import transaction +from pyramid.security import ( + Allow, + Everyone, + ) -from pyramid.security import Allow -from pyramid.security import Everyone +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Text - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension @@ -29,20 +32,6 @@ class Page(Base): 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', 'This is the front page') - session.add(page) - transaction.commit() - except IntegrityError: - # already created - transaction.abort() - class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/populate.py new file mode 100644 index 000000000..981adff38 --- /dev/null +++ b/docs/tutorials/wiki2/src/authorization/tutorial/scripts/populate.py @@ -0,0 +1,35 @@ +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, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv, settings=None): + 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) + Base.metadata.create_all(engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/security.py b/docs/tutorials/wiki2/src/authorization/tutorial/security.py index cfd13071e..d88c9c71f 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/security.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/security.py @@ -5,4 +5,3 @@ GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) - diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py index 332031ba4..31d2dc6d5 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/tests.py @@ -1,15 +1,20 @@ import unittest - +import transaction from pyramid import testing def _initTestingDB(): - from tutorial.models import DBSession - from tutorial.models import Base from sqlalchemy import create_engine + from tutorial.models import ( + DBSession, + Page, + Base + ) engine = create_engine('sqlite://') - DBSession.configure(bind=engine) - Base.metadata.bind = engine Base.metadata.create_all(engine) + DBSession.configure(bind=engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) return DBSession def _registerRoutes(config): @@ -20,14 +25,16 @@ def _registerRoutes(config): class ViewWikiTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() + self.session = _initTestingDB() def tearDown(self): + self.session.remove() 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() @@ -40,6 +47,7 @@ class ViewPageTests(unittest.TestCase): self.config = testing.setUp() def tearDown(self): + self.session.remove() testing.tearDown() def _callFUT(self, request): @@ -121,7 +129,8 @@ class EditPageTests(unittest.TestCase): self.session.add(page) info = self._callFUT(request) self.assertEqual(info['page'], page) - self.assertEqual(info['save_url'], 'http://example.com/abc/edit_page') + self.assertEqual(info['save_url'], + 'http://example.com/abc/edit_page') def test_it_submitted(self): from tutorial.models import Page diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py index fc85d4585..375f1f5a5 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py @@ -1,20 +1,36 @@ import re - from docutils.core import publish_parts -from pyramid.httpexceptions import HTTPFound, HTTPNotFound -from pyramid.security import authenticated_userid +from pyramid.httpexceptions import ( + HTTPFound, + HTTPNotFound, + HTTPForbidden, + ) + +from pyramid.view import view_config + +from pyramid.security import ( + remember, + forget, + authenticated_userid, + ) -from tutorial.models import DBSession -from tutorial.models import Page +from .models import ( + DBSession, + Page, + ) + +from .security import USERS # 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')) +@view_config(route_name='view_page', renderer='templates/view.pt') def view_page(request): pagename = request.matchdict['pagename'] session = DBSession() @@ -35,10 +51,11 @@ def view_page(request): content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.route_url('edit_page', pagename=pagename) - logged_in = authenticated_userid(request) return dict(page=page, content=content, edit_url=edit_url, - logged_in=logged_in) + logged_in=authenticated_userid(request)) +@view_config(route_name='add_page', renderer='templates/edit.pt', + permission='edit') def add_page(request): name = request.matchdict['pagename'] if 'form.submitted' in request.params: @@ -50,9 +67,11 @@ def add_page(request): pagename=name)) save_url = request.route_url('add_page', pagename=name) page = Page('', '') - logged_in = authenticated_userid(request) - return dict(page=page, save_url=save_url, logged_in=logged_in) + return dict(page=page, save_url=save_url, + logged_in=authenticated_userid(request)) +@view_config(route_name='edit_page', renderer='templates/edit.pt', + permission='edit') def edit_page(request): name = request.matchdict['pagename'] session = DBSession() @@ -62,10 +81,43 @@ def edit_page(request): session.add(page) return HTTPFound(location = request.route_url('view_page', pagename=name)) - - logged_in = authenticated_userid(request) return dict( page=page, save_url = request.route_url('edit_page', pagename=name), - logged_in = logged_in, + logged_in=authenticated_userid(request), ) + +@view_config(route_name='login', renderer='templates/login.pt') +@view_config(context=HTTPForbidden, renderer='templates/login.pt') +def login(request): + login_url = request.route_url('login') + referrer = request.url + if referrer == login_url: + referrer = '/' # never use the login form itself as came_from + came_from = request.params.get('came_from', referrer) + message = '' + login = '' + password = '' + if 'form.submitted' in request.params: + login = request.params['login'] + password = request.params['password'] + if USERS.get(login) == password: + headers = remember(request, login) + return HTTPFound(location = came_from, + headers = headers) + message = 'Failed login' + + return dict( + message = message, + url = request.application_url + '/login', + came_from = came_from, + login = login, + password = password, + ) + +@view_config(route_name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location = request.route_url('view_wiki'), + headers = headers) + diff --git a/docs/tutorials/wiki2/src/basiclayout/README.txt b/docs/tutorials/wiki2/src/basiclayout/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/basiclayout/README.txt +++ b/docs/tutorials/wiki2/src/basiclayout/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/basiclayout/production.ini +++ b/docs/tutorials/wiki2/src/basiclayout/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/basiclayout/setup.py b/docs/tutorials/wiki2/src/basiclayout/setup.py index 3ab493912..0ca918cab 100644 --- a/docs/tutorials/wiki2/src/basiclayout/setup.py +++ b/docs/tutorials/wiki2/src/basiclayout/setup.py @@ -41,6 +41,8 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py index 1f2cfd307..253341563 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py @@ -13,3 +13,4 @@ def main(global_config, **settings): config.add_route('home', '/') config.scan() return config.make_wsgi_app() + diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py index 9b687931b..b6ac15429 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py @@ -1,43 +1,28 @@ -import transaction +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Unicode - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension -DBSession = scoped_session(sessionmaker( - extension=ZopeTransactionExtension())) +DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class MyModel(Base): __tablename__ = 'models' id = Column(Integer, primary_key=True) - name = Column(Unicode(255), unique=True) + name = Column(Text, unique=True) value = Column(Integer) def __init__(self, name, value): self.name = name self.value = value -def populate(): - session = DBSession() - model = MyModel(name=u'root',value=55) - session.add(model) - session.flush() - transaction.commit() - -def initialize_sql(engine): - DBSession.configure(bind=engine) - Base.metadata.bind = engine - Base.metadata.create_all(engine) - try: - populate() - except IntegrityError: - transaction.abort() diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/populate.py new file mode 100644 index 000000000..0e828465f --- /dev/null +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/populate.py @@ -0,0 +1,35 @@ +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, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv): + 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) + Base.metadata.create_all(engine) + with transaction.manager: + model = MyModel(name='one', value=1) + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt index 14b88d16a..fbfa9870b 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt @@ -6,9 +6,9 @@ <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" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <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]--> diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py index 5efa6affa..653d061e4 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py @@ -1,23 +1,32 @@ import unittest +import transaction + from pyramid import testing -def _initTestingDB(): - from sqlalchemy import create_engine - from tutorial.models import initialize_sql - session = initialize_sql(create_engine('sqlite://')) - return session +from .models import DBSession class TestMyView(unittest.TestCase): def setUp(self): self.config = testing.setUp() - _initTestingDB() + from sqlalchemy import create_engine + engine = create_engine('sqlite://') + from .models import ( + Base, + MyModel, + ) + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = MyModel(name='one', value=55) + DBSession.add(model) def tearDown(self): + DBSession.remove() testing.tearDown() def test_it(self): - from tutorial.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['root'].name, 'root') + self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'tutorial') diff --git a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py index 631af9b6a..3e6abf2c2 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py @@ -1,9 +1,11 @@ from pyramid.view import view_config -from .models import DBSession -from .models import MyModel +from .models import ( + DBSession, + MyModel, + ) @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): - one = DBSession.query(MyModel).filter(MyModel.name==u'root').first() + one = DBSession.query(MyModel).filter(MyModel.name=='one').first() return {'one':one, 'project':'tutorial'} diff --git a/docs/tutorials/wiki2/src/models/README.txt b/docs/tutorials/wiki2/src/models/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/models/README.txt +++ b/docs/tutorials/wiki2/src/models/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/models/setup.py b/docs/tutorials/wiki2/src/models/setup.py index 3ab493912..0ca918cab 100644 --- a/docs/tutorials/wiki2/src/models/setup.py +++ b/docs/tutorials/wiki2/src/models/setup.py @@ -41,6 +41,8 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) diff --git a/docs/tutorials/wiki2/src/models/tutorial/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/__init__.py index fda7c9ce6..253341563 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/models/tutorial/__init__.py @@ -1,16 +1,16 @@ from pyramid.config import Configurator from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql +from .models import DBSession def main(global_config, **settings): - """ This function returns a WSGI application. + """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) config = Configurator(settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') - config.add_view('tutorial.views.my_view', route_name='home', - renderer='templates/mytemplate.pt') + config.scan() return config.make_wsgi_app() + diff --git a/docs/tutorials/wiki2/src/models/tutorial/models.py b/docs/tutorials/wiki2/src/models/tutorial/models.py index 30f77a0b9..499396c5b 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/models.py +++ b/docs/tutorials/wiki2/src/models/tutorial/models.py @@ -1,19 +1,19 @@ -import transaction +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Text - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension -DBSession = scoped_session(sessionmaker( - extension=ZopeTransactionExtension())) +DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class Page(Base): @@ -27,16 +27,3 @@ class Page(Base): 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', 'This is the front page') - session.add(page) - transaction.commit() - except IntegrityError: - # already created - transaction.abort() diff --git a/docs/tutorials/wiki2/src/models/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/models/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/models/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/models/tutorial/scripts/populate.py new file mode 100644 index 000000000..03188e8ad --- /dev/null +++ b/docs/tutorials/wiki2/src/models/tutorial/scripts/populate.py @@ -0,0 +1,35 @@ +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, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv): + 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) + Base.metadata.create_all(engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt index 14b88d16a..fbfa9870b 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt @@ -6,9 +6,9 @@ <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" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <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]--> diff --git a/docs/tutorials/wiki2/src/models/tutorial/tests.py b/docs/tutorials/wiki2/src/models/tutorial/tests.py index 71f5e21e3..653d061e4 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/models/tutorial/tests.py @@ -1,22 +1,32 @@ import unittest +import transaction + from pyramid import testing -def _initTestingDB(): - from tutorial.models import initialize_sql - session = initialize_sql('sqlite://') - return session +from .models import DBSession class TestMyView(unittest.TestCase): def setUp(self): self.config = testing.setUp() - _initTestingDB() + from sqlalchemy import create_engine + engine = create_engine('sqlite://') + from .models import ( + Base, + MyModel, + ) + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = MyModel(name='one', value=55) + DBSession.add(model) def tearDown(self): + DBSession.remove() testing.tearDown() def test_it(self): - from tutorial.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) - self.assertEqual(info['root'].name, 'root') + self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'tutorial') diff --git a/docs/tutorials/wiki2/src/models/tutorial/views.py b/docs/tutorials/wiki2/src/models/tutorial/views.py index e550e3257..3e6abf2c2 100644 --- a/docs/tutorials/wiki2/src/models/tutorial/views.py +++ b/docs/tutorials/wiki2/src/models/tutorial/views.py @@ -1,7 +1,11 @@ -from tutorial.models import DBSession -from tutorial.models import MyModel +from pyramid.view import view_config +from .models import ( + DBSession, + MyModel, + ) + +@view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): - dbsession = DBSession() - root = dbsession.query(MyModel).filter(MyModel.name==u'root').first() - return {'root':root, 'project':'tutorial'} + one = DBSession.query(MyModel).filter(MyModel.name=='one').first() + return {'one':one, 'project':'tutorial'} diff --git a/docs/tutorials/wiki2/src/tests/README.txt b/docs/tutorials/wiki2/src/tests/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/tests/README.txt +++ b/docs/tutorials/wiki2/src/tests/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/tests/development.ini +++ b/docs/tutorials/wiki2/src/tests/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/tests/production.ini +++ b/docs/tutorials/wiki2/src/tests/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py index 6de8a1fbe..f965ccc6e 100644 --- a/docs/tutorials/wiki2/src/tests/setup.py +++ b/docs/tutorials/wiki2/src/tests/setup.py @@ -43,6 +43,7 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) - diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py index cca52fdfe..04dd5fe82 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py @@ -4,14 +4,15 @@ from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql from tutorial.security import groupfinder +from .models import DBSession + def main(global_config, **settings): - """ This function returns a WSGI application. + """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder) authz_policy = ACLAuthorizationPolicy() @@ -19,27 +20,13 @@ def main(global_config, **settings): root_factory='tutorial.models.RootFactory', authentication_policy=authn_policy, authorization_policy=authz_policy) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('login', '/login') config.add_route('logout', '/logout') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') - - config.add_view('tutorial.views.view_wiki', route_name='view_wiki') - config.add_view('tutorial.login.login', route_name='login', - renderer='tutorial:templates/login.pt') - config.add_view('tutorial.login.logout', route_name='logout') - config.add_view('tutorial.views.view_page', route_name='view_page', - renderer='tutorial:templates/view.pt') - config.add_view('tutorial.views.add_page', route_name='add_page', - renderer='tutorial:templates/edit.pt', permission='edit') - config.add_view('tutorial.views.edit_page', route_name='edit_page', - renderer='tutorial:templates/edit.pt', permission='edit') - config.add_view('tutorial.login.login', - context='pyramid.httpexceptions.HTTPForbidden', - renderer='tutorial:templates/login.pt') + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki2/src/tests/tutorial/login.py b/docs/tutorials/wiki2/src/tests/tutorial/login.py deleted file mode 100644 index 5a825d8d6..000000000 --- a/docs/tutorials/wiki2/src/tests/tutorial/login.py +++ /dev/null @@ -1,37 +0,0 @@ -from pyramid.httpexceptions import HTTPFound -from pyramid.security import remember -from pyramid.security import forget - -from tutorial.security import USERS - -def login(request): - login_url = request.route_url('login') - referrer = request.url - if referrer == login_url: - referrer = '/' # never use the login form itself as came_from - came_from = request.params.get('came_from', referrer) - message = '' - login = '' - password = '' - if 'form.submitted' in request.params: - login = request.params['login'] - password = request.params['password'] - if USERS.get(login) == password: - headers = remember(request, login) - return HTTPFound(location = came_from, - headers = headers) - message = 'Failed login' - - return dict( - message = message, - url = request.application_url + '/login', - came_from = came_from, - login = login, - password = password, - ) - -def logout(request): - headers = forget(request) - return HTTPFound(location = request.route_url('view_wiki'), - headers = headers) - diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models.py b/docs/tutorials/wiki2/src/tests/tutorial/models.py index 832545cb1..c3bdcbea5 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/models.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/models.py @@ -1,17 +1,20 @@ -import transaction +from pyramid.security import ( + Allow, + Everyone, + ) -from pyramid.security import Allow -from pyramid.security import Everyone +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Text - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension @@ -29,20 +32,6 @@ class Page(Base): 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', 'This is the front page') - session.add(page) - transaction.commit() - except IntegrityError: - # already created - transaction.abort() - class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] diff --git a/docs/tutorials/wiki2/src/tests/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/tests/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/tests/tutorial/scripts/populate.py new file mode 100644 index 000000000..de74f4d63 --- /dev/null +++ b/docs/tutorials/wiki2/src/tests/tutorial/scripts/populate.py @@ -0,0 +1,36 @@ +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, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv, settings=None): + if len(argv) != 2: + usage(argv) + config_uri = argv[1] + if settings is None: + setup_logging(config_uri) + settings = get_appsettings(config_uri) + engine = engine_from_config(settings, 'sqlalchemy.') + DBSession.configure(bind=engine) + Base.metadata.create_all(engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security.py b/docs/tutorials/wiki2/src/tests/tutorial/security.py index cfd13071e..d88c9c71f 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/security.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/security.py @@ -5,4 +5,3 @@ GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) - diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests.py b/docs/tutorials/wiki2/src/tests/tutorial/tests.py index 8439e2748..557d1b1be 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/tests.py @@ -1,16 +1,20 @@ import unittest - +import transaction 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 + 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('FrontPage', 'This is the front page') + DBSession.add(model) return DBSession def _registerRoutes(config): @@ -39,28 +43,6 @@ class PageModelTests(unittest.TestCase): 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() @@ -191,10 +173,11 @@ class FunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main - settings = { 'sqlalchemy.url': 'sqlite:///:memory:'} + settings = { 'sqlalchemy.url': 'sqlite://'} app = main({}, **settings) from webtest import TestApp self.testapp = TestApp(app) + _initTestingDB() def tearDown(self): del self.testapp @@ -263,3 +246,23 @@ class FunctionalTests(unittest.TestCase): self.testapp.get(self.editor_login, status=302) res = self.testapp.get('/FrontPage', status=200) self.assertTrue('FrontPage' in res.body) + +class Test_populate(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, settings): + from tutorial.scripts.populate import main + main(['foo', 'development.ini'], settings) + + def test_it(self): + self._callFUT({'sqlalchemy.url':'sqlite://'}) + from tutorial.models import DBSession, Page + self.assertEqual(DBSession.query(Page).one().data, + 'This is the front page') + diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py index fc85d4585..375f1f5a5 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/views.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/views.py @@ -1,20 +1,36 @@ import re - from docutils.core import publish_parts -from pyramid.httpexceptions import HTTPFound, HTTPNotFound -from pyramid.security import authenticated_userid +from pyramid.httpexceptions import ( + HTTPFound, + HTTPNotFound, + HTTPForbidden, + ) + +from pyramid.view import view_config + +from pyramid.security import ( + remember, + forget, + authenticated_userid, + ) -from tutorial.models import DBSession -from tutorial.models import Page +from .models import ( + DBSession, + Page, + ) + +from .security import USERS # 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')) +@view_config(route_name='view_page', renderer='templates/view.pt') def view_page(request): pagename = request.matchdict['pagename'] session = DBSession() @@ -35,10 +51,11 @@ def view_page(request): content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.route_url('edit_page', pagename=pagename) - logged_in = authenticated_userid(request) return dict(page=page, content=content, edit_url=edit_url, - logged_in=logged_in) + logged_in=authenticated_userid(request)) +@view_config(route_name='add_page', renderer='templates/edit.pt', + permission='edit') def add_page(request): name = request.matchdict['pagename'] if 'form.submitted' in request.params: @@ -50,9 +67,11 @@ def add_page(request): pagename=name)) save_url = request.route_url('add_page', pagename=name) page = Page('', '') - logged_in = authenticated_userid(request) - return dict(page=page, save_url=save_url, logged_in=logged_in) + return dict(page=page, save_url=save_url, + logged_in=authenticated_userid(request)) +@view_config(route_name='edit_page', renderer='templates/edit.pt', + permission='edit') def edit_page(request): name = request.matchdict['pagename'] session = DBSession() @@ -62,10 +81,43 @@ def edit_page(request): session.add(page) return HTTPFound(location = request.route_url('view_page', pagename=name)) - - logged_in = authenticated_userid(request) return dict( page=page, save_url = request.route_url('edit_page', pagename=name), - logged_in = logged_in, + logged_in=authenticated_userid(request), ) + +@view_config(route_name='login', renderer='templates/login.pt') +@view_config(context=HTTPForbidden, renderer='templates/login.pt') +def login(request): + login_url = request.route_url('login') + referrer = request.url + if referrer == login_url: + referrer = '/' # never use the login form itself as came_from + came_from = request.params.get('came_from', referrer) + message = '' + login = '' + password = '' + if 'form.submitted' in request.params: + login = request.params['login'] + password = request.params['password'] + if USERS.get(login) == password: + headers = remember(request, login) + return HTTPFound(location = came_from, + headers = headers) + message = 'Failed login' + + return dict( + message = message, + url = request.application_url + '/login', + came_from = came_from, + login = login, + password = password, + ) + +@view_config(route_name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location = request.route_url('view_wiki'), + headers = headers) + diff --git a/docs/tutorials/wiki2/src/views/README.txt b/docs/tutorials/wiki2/src/views/README.txt index d41f7f90f..6f851e9b7 100644 --- a/docs/tutorials/wiki2/src/views/README.txt +++ b/docs/tutorials/wiki2/src/views/README.txt @@ -1,4 +1 @@ tutorial README - - - diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index d1e262324..4f7493cba 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false @@ -19,7 +20,7 @@ port = 6543 # Begin logging configuration [loggers] -keys = root, sqlalchemy +keys = root, tutorial, sqlalchemy [handlers] keys = console @@ -31,6 +32,11 @@ keys = generic level = INFO handlers = console +[logger_tutorial] +level = DEBUG +handlers = +qualname = tutorial + [logger_sqlalchemy] level = INFO handlers = diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini index ac02acf3f..53eaf20a1 100644 --- a/docs/tutorials/wiki2/src/views/production.ini +++ b/docs/tutorials/wiki2/src/views/production.ini @@ -1,5 +1,6 @@ [app:main] use = egg:tutorial + pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false diff --git a/docs/tutorials/wiki2/src/views/setup.py b/docs/tutorials/wiki2/src/views/setup.py index 439a86923..9c0e88eb0 100644 --- a/docs/tutorials/wiki2/src/views/setup.py +++ b/docs/tutorials/wiki2/src/views/setup.py @@ -42,6 +42,8 @@ setup(name='tutorial', entry_points = """\ [paste.app_factory] main = tutorial:main + [console_scripts] + populate_tutorial = tutorial.scripts.populate:main """, ) diff --git a/docs/tutorials/wiki2/src/views/tutorial/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/__init__.py index 7d79f7a1f..b30d593cf 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/views/tutorial/__init__.py @@ -1,25 +1,18 @@ from pyramid.config import Configurator from sqlalchemy import engine_from_config -from tutorial.models import initialize_sql +from .models import DBSession def main(global_config, **settings): - """ This function returns a WSGI application. + """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') - initialize_sql(engine) + DBSession.configure(bind=engine) config = Configurator(settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) + config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') - config.add_view('tutorial.views.view_wiki', route_name='view_wiki') - config.add_view('tutorial.views.view_page', route_name='view_page', - renderer='tutorial:templates/view.pt') - config.add_view('tutorial.views.add_page', route_name='add_page', - renderer='tutorial:templates/edit.pt') - config.add_view('tutorial.views.edit_page', route_name='edit_page', - renderer='tutorial:templates/edit.pt') + config.scan() return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki2/src/views/tutorial/models.py b/docs/tutorials/wiki2/src/views/tutorial/models.py index 30506f67e..499396c5b 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/models.py +++ b/docs/tutorials/wiki2/src/views/tutorial/models.py @@ -1,14 +1,15 @@ -import transaction +from sqlalchemy import ( + Column, + Integer, + Text, + ) -from sqlalchemy import Column -from sqlalchemy import Integer -from sqlalchemy import Text - -from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import scoped_session -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import ( + scoped_session, + sessionmaker, + ) from zope.sqlalchemy import ZopeTransactionExtension @@ -26,16 +27,3 @@ class Page(Base): 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) - transaction.commit() - except IntegrityError: - # already created - transaction.abort() diff --git a/docs/tutorials/wiki2/src/views/tutorial/scripts/__init__.py b/docs/tutorials/wiki2/src/views/tutorial/scripts/__init__.py new file mode 100644 index 000000000..5bb534f79 --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/scripts/__init__.py @@ -0,0 +1 @@ +# package diff --git a/docs/tutorials/wiki2/src/views/tutorial/scripts/populate.py b/docs/tutorials/wiki2/src/views/tutorial/scripts/populate.py new file mode 100644 index 000000000..03188e8ad --- /dev/null +++ b/docs/tutorials/wiki2/src/views/tutorial/scripts/populate.py @@ -0,0 +1,35 @@ +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, + Base, + ) + +def usage(argv): + cmd = os.path.basename(argv[0]) + print('usage: %s <config_uri>\n' + '(example: "%s development.ini")' % (cmd, cmd)) + sys.exit(1) + +def main(argv=sys.argv): + 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) + Base.metadata.create_all(engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) diff --git a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt index 14b88d16a..fbfa9870b 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt @@ -6,9 +6,9 @@ <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" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/nobile/stylesheet.css" media="screen" /> <link rel="stylesheet" href="http://static.pylonsproject.org/fonts/neuton/stylesheet.css" media="screen" /> - <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]--> diff --git a/docs/tutorials/wiki2/src/views/tutorial/tests.py b/docs/tutorials/wiki2/src/views/tutorial/tests.py index 668bf5479..31d2dc6d5 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/tests.py +++ b/docs/tutorials/wiki2/src/views/tutorial/tests.py @@ -1,15 +1,20 @@ import unittest - +import transaction from pyramid import testing def _initTestingDB(): - from tutorial.models import DBSession - from tutorial.models import Base from sqlalchemy import create_engine + from tutorial.models import ( + DBSession, + Page, + Base + ) engine = create_engine('sqlite://') - DBSession.configure(bind=engine) - Base.metadata.bind = engine Base.metadata.create_all(engine) + DBSession.configure(bind=engine) + with transaction.manager: + model = Page('FrontPage', 'This is the front page') + DBSession.add(model) return DBSession def _registerRoutes(config): @@ -20,8 +25,10 @@ def _registerRoutes(config): class ViewWikiTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() + self.session = _initTestingDB() def tearDown(self): + self.session.remove() testing.tearDown() def _callFUT(self, request): diff --git a/docs/tutorials/wiki2/src/views/tutorial/views.py b/docs/tutorials/wiki2/src/views/tutorial/views.py index e04b96ae4..5c49dd2e8 100644 --- a/docs/tutorials/wiki2/src/views/tutorial/views.py +++ b/docs/tutorials/wiki2/src/views/tutorial/views.py @@ -1,19 +1,26 @@ import re - from docutils.core import publish_parts -from pyramid.httpexceptions import HTTPFound, HTTPNotFound +from pyramid.httpexceptions import ( + HTTPFound, + HTTPNotFound, + ) +from pyramid.view import view_config -from tutorial.models import DBSession -from tutorial.models import Page +from .models import ( + DBSession, + 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')) +@view_config(route_name='view_page', renderer='templates/view.pt') def view_page(request): pagename = request.matchdict['pagename'] session = DBSession() @@ -36,6 +43,7 @@ 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') def add_page(request): name = request.matchdict['pagename'] if 'form.submitted' in request.params: @@ -49,6 +57,7 @@ def add_page(request): page = Page('', '') return dict(page=page, save_url=save_url) +@view_config(route_name='edit_page', renderer='templates/edit.pt') def edit_page(request): name = request.matchdict['pagename'] session = DBSession() diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst index 8fa8ea3ae..25cf2c25e 100644 --- a/docs/tutorials/wiki2/tests.rst +++ b/docs/tutorials/wiki2/tests.rst @@ -14,8 +14,8 @@ for the ``initialize_sql`` function. To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a result of the ``alchemy`` scaffold. We'll add two test classes: one for the -``Page`` model named ``PageModelTests``, one for the ``initialize_sql`` -function named ``InitializeSqlTests``. +``Page`` model named ``PageModelTests``, and one for the ``populate`` script +named ``Test_populate``. Testing the Views ================= |
