diff options
Diffstat (limited to 'docs/tutorials')
101 files changed, 1407 insertions, 1019 deletions
diff --git a/docs/tutorials/wiki/NOTE-relocatable.txt b/docs/tutorials/wiki/NOTE-relocatable.txt new file mode 100644 index 000000000..cec2639f3 --- /dev/null +++ b/docs/tutorials/wiki/NOTE-relocatable.txt @@ -0,0 +1,13 @@ +We specifically use relative package references where possible so this demo +works even if the user names their package (in the 'bin/paster create -t +zodb ...' step) something other than 'tutorial'. + +Specifically: + +- use relative imports +- use plain relative URLs for resources (like stylesheets and images) in + page templates. + +Direct uses of the package name, like in __init__.py 'config.scan()' +statements, are already adjusted by the paster/pcreate, so we don't have to +worry about them. diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 1835ce7ea..fa18d4a41 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -4,7 +4,7 @@ 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 people whom are members of a *group* named +our application to allow people who are members of a *group* named ``group:editors`` 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 @@ -27,8 +27,8 @@ The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/ <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/>`_. -Adding Authentication and Authorization Policies -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add Authentication and Authorization Policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We'll change our package's ``__init__.py`` file to enable an ``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable @@ -42,7 +42,7 @@ declarative security checking. We need to import the new policies: Then, we'll add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 20-25 + :lines: 17-22 :linenos: :language: python @@ -60,8 +60,8 @@ look like so: :linenos: :language: python -Adding ``security.py`` -~~~~~~~~~~~~~~~~~~~~~~ +Add ``security.py`` +~~~~~~~~~~~~~~~~~~~ Add a ``security.py`` module within your package (in the same directory as ``__init__.py``, ``views.py``, etc.) with the following @@ -73,17 +73,16 @@ content: The ``groupfinder`` function defined here is an :term:`authentication policy` "callback"; it is a callable that accepts a userid and a request. If the -userid exists in the system, the callback will -return a sequence of group identifiers (or an empty sequence if the user -isn't a member of any groups). If the userid *does not* exist in the system, -the callback will return ``None``. In a production system, user and group data will -most often come from a database, but here we use "dummy" data to represent -user and groups sources. Note that the ``editor`` user is a member of the -``group:editors`` group in our dummy group data (the ``GROUPS`` data -structure). - -Giving Our Root Resource an ACL -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +userid exists in the system, the callback will return a sequence of group +identifiers (or an empty sequence if the user isn't a member of any groups). +If the userid *does not* exist in the system, the callback will return +``None``. In a production system, user and group data will most often come +from a database, but here we use "dummy" data to represent user and groups +sources. Note that the ``editor`` user is a member of the ``group:editors`` +group in our dummy group data (the ``GROUPS`` data structure). + +Give Our Root Resource an ACL +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We need to give our root resource object an :term:`ACL`. This ACL will be sufficient to provide enough information to the :app:`Pyramid` security @@ -119,8 +118,8 @@ Our resulting ``models.py`` file will now look like so: :linenos: :language: python -Adding Login and Logout Views -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add Login and Logout Views +~~~~~~~~~~~~~~~~~~~~~~~~~~ We'll add a ``login`` view which renders a login form and processes the post from the login form, checking credentials. @@ -129,18 +128,24 @@ We'll also add a ``logout`` view 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 logout views. Add a file named ``login.py`` to your application -(in the same directory as ``views.py``) with the following content: +We'll add these views to the existing ``views.py`` file we have in our +project. Here's what the ``login`` view callable will look like: + +.. literalinclude:: src/authorization/tutorial/views.py + :pyobject: login + :linenos: + :language: python + +Here's what the ``logout`` view callable will look like: -.. literalinclude:: src/authorization/tutorial/login.py +.. literalinclude:: src/authorization/tutorial/views.py + :pyobject: logout :linenos: :language: python -Note that the ``login`` view callable in the ``login.py`` file has *two* view -configuration decorators. The order of these decorators is unimportant. -Each just adds a different :term:`view configuration` for the ``login`` view -callable. +Note that the ``login`` view callable has *two* view configuration +decorators. The order of these decorators is unimportant. Each just adds a +different :term:`view configuration` for the ``login`` view callable. The first view configuration decorator configures the ``login`` view callable so it will be invoked when someone visits ``/login`` (when the context is a @@ -157,14 +162,18 @@ login form. Before being allowed to continue on to the add or edit form, he will have to provide credentials that give him permission to add or edit via this login form. -Changing Existing Views -~~~~~~~~~~~~~~~~~~~~~~~ +Note that we're relying on some additional imports within the bodies of these +views (e.g. ``remember`` and ``forget``). We'll see a rendering of the +entire views.py file a little later here to show you where those come from. -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 -into its template. We'll add something like this to each view body: +Change Existing Views +~~~~~~~~~~~~~~~~~~~~~ + +In order to indicate whether the current user is logged in, we need to change +each of our ``view_page``, ``edit_page`` and ``add_page`` views in +``views.py`` to pass a "logged in" parameter into its template. We'll add +something like this to each view body: -.. ignore-next-block .. code-block:: python :linenos: @@ -175,7 +184,6 @@ We'll then change the return value of each view that has an associated ``renderer`` to pass the resulting ``logged_in`` value to the template. For example: -.. ignore-next-block .. code-block:: python :linenos: @@ -184,8 +192,8 @@ template. For example: logged_in = logged_in, edit_url = edit_url) -Adding ``permission`` Declarations to our ``view_config`` Decorators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add ``permission`` Declarations to our ``view_config`` Decorators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To protect each of our views with a particular permission, we need to pass a ``permission`` argument to each of our :class:`pyramid.view.view_config` @@ -216,11 +224,11 @@ decorators. To do so, within ``views.py``: function consults the ``GROUPS`` data structure. This means that the ``editor`` user can add and edit pages. -Adding the ``login.pt`` Template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add the ``login.pt`` Template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Add a ``login.pt`` template to your templates directory. It's -referred to within the login view we just added to ``login.py``. +referred to within the login view we just added to ``views.py``. .. literalinclude:: src/authorization/tutorial/templates/login.pt :language: xml @@ -241,8 +249,8 @@ class="app-welcome align-right">`` div: <a href="${request.application_url}/logout">Logout</a> </span> -Seeing Our Changes To ``views.py`` and our Templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +See Our Changes To ``views.py`` and our Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Our ``views.py`` module will look something like this when we're done: @@ -262,8 +270,8 @@ Our ``view.pt`` template will look something like this when we're done: :linenos: :language: xml -Viewing the Application in a Browser -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +View the Application in a Browser +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We can finally examine our application in a browser. The views we'll try are as follows: diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index 47cac597b..56f817a85 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -2,7 +2,7 @@ Basic Layout ============ -The starter files generated by the ``pyramid_zodb`` scaffold are basic, but +The starter files generated by the ``zodb`` scaffold are basic, but they provide a good orientation for the high-level patterns common to most :term:`traversal` -based :app:`Pyramid` (and :term:`ZODB` based) projects. @@ -10,8 +10,8 @@ The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/ <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/>`_. -App Startup with ``__init__.py`` --------------------------------- +Appplication Configuration with ``__init__.py`` +------------------------------------------------ A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file. Even if empty, this marks a directory as a Python @@ -35,7 +35,7 @@ point happens to be the ``main`` function within the file named #. *Line 12*. We construct a :term:`Configurator` with a :term:`root factory` and the settings keywords parsed by :term:`PasteDeploy`. The root - factory is named ``get_root``. + factory is named ``root_factory``. #. *Line 13*. Register a 'static view' which answers requests which start with with URL path ``/static`` using the @@ -45,16 +45,19 @@ point happens to be the ``main`` function within the file named ``http://localhost:6543/static/`` and below. The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``. the The second argument of this tag is the "path", - which is an :term:`asset specification`, so it finds the resources it - should serve within the ``static`` directory inside the ``tutorial`` - package. + which is a relative :term:`asset specification`, so it finds the resources + it should serve within the ``static`` directory inside the ``tutorial`` + package. The scaffold could have alternately used an *absolute* asset + specification as the path (``tutorial:static``) but it does not. #. *Line 14*. Perform a :term:`scan`. A scan will find :term:`configuration - decoration`, such as view configuration decorators - (e.g. ``@view_config``) in the source code of the ``tutorial`` package and - will take actions based on these decorators. The argument to - :meth:`~pyramid.config.Configurator.scan` is the package name to scan, - which is ``tutorial``. + decoration`, such as view configuration decorators (e.g. ``@view_config``) + in the source code of the ``tutorial`` package and will take actions based + on these decorators. We don't pass any arguments to + :meth:`~pyramid.config.Configurator.scan`, which implies that the scan + should take place in the current package (in this case, ``tutorial``). + The scaffold could have equivalently said ``config.scan('tutorial')`` but + it chose to omit the package name argument. #. *Line 15*. Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method @@ -69,7 +72,7 @@ hierarchically in a :term:`resource tree`. This tree is consulted by tree represents the site structure, but it *also* represents the :term:`domain model` of the application, because each resource is a node stored persistently in a :term:`ZODB` database. The ``models.py`` file is -where the ``pyramid_zodb`` scaffold put the classes that implement our +where the ``zodb`` scaffold put the classes that implement our resource objects, each of which happens also to be a domain model object. Here is the source for ``models.py``: @@ -119,7 +122,7 @@ Let's try to understand the components in this module: decoration` to perform a :term:`view configuration` registration. This view configuration registration will be activated when the application is started. It will be activated by virtue of it being found as the result - of a :term:`scan` (when Line 17 of ``__init__.py`` is run). + of a :term:`scan` (when Line 14 of ``__init__.py`` is run). The ``@view_config`` decorator accepts a number of keyword arguments. We use two keyword arguments here: ``context`` and ``renderer``. @@ -131,12 +134,15 @@ Let's try to understand the components in this module: model, this view callable will be invoked. The ``renderer`` argument names an :term:`asset specification` of - ``tutorial:templates/mytemplate.pt``. This asset specification points at - a :term:`Chameleon` template which lives in the ``mytemplate.pt`` file + ``templates/mytemplate.pt``. This asset specification points at a + :term:`Chameleon` template which lives in the ``mytemplate.pt`` file within the ``templates`` directory of the ``tutorial`` package. And indeed if you look in the ``templates`` directory of this package, you'll see a ``mytemplate.pt`` template file, which renders the default home page - of the generated project. + of the generated project. This asset specification is *relative* (to the + view.py's current package). We could have alternately an used the + absolute asset specification ``tutorial:templates/mytemplate.pt``, but + chose to use the relative version. Since this call to ``@view_config`` doesn't pass a ``name`` argument, the ``my_view`` function which it decorates represents the "default" view @@ -144,7 +150,7 @@ Let's try to understand the components in this module: #. *Lines 5-6*. We define a :term:`view callable` named ``my_view``, which we decorated in the step above. This view callable is a *function* we - write generated by the ``pyramid_zodb`` scaffold that is given a + write generated by the ``zodb`` scaffold that is given a ``request`` and which returns a dictionary. The ``mytemplate.pt`` :term:`renderer` named by the asset specification in the step above will convert this dictionary to a :term:`response` on our behalf. diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index ee9c13ab2..cdf3b6092 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -18,8 +18,8 @@ The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/models/ <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/models/>`_. -Deleting the Database ---------------------- +Delete the Database +------------------- In the next step, we're going to remove the ``MyModel`` Python model class from our ``models.py`` file. Since this class is referred to within @@ -30,8 +30,8 @@ directory before proceeding any further. It's always fine to do this as long as you don't care about the content of the database; the database itself will be recreated as necessary. -Making Edits to ``models.py`` ------------------------------ +Edit ``models.py`` +------------------ .. note:: @@ -73,8 +73,8 @@ front page) into the Wiki within the ``appmaker``. This will provide :term:`traversal` a :term:`resource tree` to work against when it attempts to resolve URLs to resources. -Looking at the Result of Our Edits to ``models.py`` ---------------------------------------------------- +Look at the Result of Our Edits to ``models.py`` +------------------------------------------------ The result of all of our edits to ``models.py`` will end up looking something like this: @@ -83,8 +83,8 @@ something like this: :linenos: :language: python -Viewing the Application in a Browser ------------------------------------- +View the Application in a Browser +--------------------------------- We can't. At this point, our system is in a "non-runnable" state; we'll need to change view-related files in the next chapter to be able to start the diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index c21367559..371cae8eb 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -293,12 +293,10 @@ replicate within the body of this guide, however it is available `online <http://github.com/Pylons/pyramid/blob/master/docs/tutorials/wiki/src/views/tutorial/static/pylons.css>`_. This CSS file will be accessed via -e.g. ``http://localhost:6543/static/pylons.css`` by virtue of the call to +e.g. ``/static/pylons.css`` by virtue of the call to ``add_static_view`` directive we've made in the ``__init__.py`` file. Any number and type of static assets can be placed in this directory (or -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. +subdirectories) and are just referred to by URL. Viewing the Application in a Browser ==================================== diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index c55c310ef..330b17c86 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -127,8 +127,8 @@ Preparation, Windows .. _making_a_project: -Making a Project -================ +Make a Project +============== Your next step is to create a project. :app:`Pyramid` supplies a variety of scaffolds to generate sample projects. For this tutorial, we will use the @@ -149,14 +149,18 @@ On Windows: c:\pyramidtut> Scripts\pcreate -s zodb tutorial +.. note:: You don't have to call it `tutorial` -- the code uses + relative paths for imports and finding templates and static + resources. + .. note:: If you are using Windows, the ``zodb`` scaffold doesn't currently deal gracefully with installation into a location that contains spaces in the path. If you experience startup problems, try putting both the virtualenv and the project into directories that do not contain spaces in their paths. -Installing the Project in "Development Mode" -============================================ +Install the Project in "Development Mode" +========================================= In order to do development on the project easily, you must "register" the project as a development egg in your workspace using the @@ -180,8 +184,8 @@ On Windows: .. _running_tests: -Running the Tests -================= +Run the Tests +============= After you've installed the project in development mode, you may run the tests for the project. @@ -198,48 +202,53 @@ On Windows: c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q -Starting the Application -======================== +Expose Test Coverage Information +================================ -Start the application. +You can run the ``nosetests`` command to see test coverage +information. This runs the tests in the same way that ``setup.py +test`` does but provides additional "coverage" information, exposing +which lines of your project are "covered" (or not covered) by the +tests. On UNIX: .. code-block:: text - $ ../bin/pserve development.ini --reload + $ ../bin/nosetests --cover-package=tutorial --cover-erase --with-coverage On Windows: .. code-block:: text - c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload + c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^ + --cover-erase --with-coverage -Exposing Test Coverage Information -================================== +Looks like the code in the ``zodb`` scaffold for ZODB projects is +missing some test coverage, particularly in the file named +``models.py``. -You can run the ``nosetests`` command to see test coverage -information. This runs the tests in the same way that ``setup.py -test`` does but provides additional "coverage" information, exposing -which lines of your project are "covered" (or not covered) by the -tests. +Start the Application +===================== + +Start the application. On UNIX: .. code-block:: text - $ ../bin/nosetests --cover-package=tutorial --cover-erase --with-coverage + $ ../bin/pserve development.ini --reload On Windows: .. code-block:: text - c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^ - --cover-erase --with-coverage + c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload -Looks like the code in the ``pyramid_zodb`` scaffold for ZODB projects is -missing some test coverage, particularly in the file named -``models.py``. +.. note:: + + Your OS firewall, if any, may pop up a dialog asking for authorization + to allow python to accept incoming network connections. Visit the Application in a Browser ================================== @@ -252,10 +261,10 @@ page. You can read more about the purpose of the icon at :ref:`debug_toolbar`. It allows you to get information about your application while you develop. -Decisions the ``pyramid_zodb`` Scaffold Has Made For You -======================================================== +Decisions the ``zodb`` Scaffold Has Made For You +================================================ -Creating a project using the ``pyramid_zodb`` scaffold makes the following +Creating a project using the ``zodb`` scaffold makes the following assumptions: - you are willing to use :term:`ZODB` as persistent storage diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 2d6eb5ecb..20ee685ee 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -4,8 +4,8 @@ from pyramid_zodbconn import get_connection from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy -from tutorial.models import appmaker -from tutorial.security import groupfinder +from .models import appmaker +from .security import groupfinder def root_factory(request): conn = get_connection(request) @@ -20,6 +20,6 @@ def main(global_config, **settings): config = Configurator(root_factory=root_factory, settings=settings, authentication_policy=authn_policy, authorization_policy=authz_policy) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - config.scan('tutorial') + config.add_static_view('static', 'static', cache_max_age=3600) + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/authorization/tutorial/login.py b/docs/tutorials/wiki/src/authorization/tutorial/login.py deleted file mode 100644 index d608a7d0b..000000000 --- a/docs/tutorials/wiki/src/authorization/tutorial/login.py +++ /dev/null @@ -1,44 +0,0 @@ -from pyramid.httpexceptions import HTTPFound - -from pyramid.security import remember -from pyramid.security import forget -from pyramid.view import view_config - -from tutorial.security import USERS - -@view_config(context='tutorial.models.Wiki', name='login', - renderer='templates/login.pt') -@view_config(context='pyramid.httpexceptions.HTTPForbidden', - renderer='templates/login.pt') -def login(request): - login_url = request.resource_url(request.context, '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(context='tutorial.models.Wiki', name='logout') -def logout(request): - headers = forget(request) - return HTTPFound(location = request.resource_url(request.context), - headers = headers) - diff --git a/docs/tutorials/wiki/src/authorization/tutorial/security.py b/docs/tutorials/wiki/src/authorization/tutorial/security.py index cfd13071e..d88c9c71f 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/security.py +++ b/docs/tutorials/wiki/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/wiki/src/authorization/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt index f9da6c414..0d0738f7f 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt index 64e592ea9..2c7235761 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt index 14b88d16a..3597c679b 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <meta name="keywords" content="python web application" /> <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="shortcut icon" href="/static/favicon.ico" /> <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" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt index d207a0c23..9dd6540cf 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/tests.py b/docs/tutorials/wiki/src/authorization/tutorial/tests.py index a4a4e2754..77e7cce29 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/tests.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/tests.py @@ -5,7 +5,7 @@ from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Page + from .models import Page return Page def _makeOne(self, data=u'some data'): @@ -14,11 +14,11 @@ class PageModelTests(unittest.TestCase): def test_constructor(self): instance = self._makeOne() self.assertEqual(instance.data, u'some data') - + class WikiModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Wiki + from .models import Wiki return Wiki def _makeOne(self): @@ -31,7 +31,7 @@ class WikiModelTests(unittest.TestCase): class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): - from tutorial.models import appmaker + from .models import appmaker return appmaker(zodb_root) def test_it(self): @@ -42,7 +42,7 @@ class AppmakerTests(unittest.TestCase): class ViewWikiTests(unittest.TestCase): def test_it(self): - from tutorial.views import view_wiki + from .views import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) @@ -50,7 +50,7 @@ class ViewWikiTests(unittest.TestCase): class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import view_page + from .views import view_page return view_page(context, request) def test_it(self): @@ -72,11 +72,11 @@ class ViewPageTests(unittest.TestCase): '</p>\n</div>\n') self.assertEqual(info['edit_url'], 'http://example.com/thepage/edit_page') - - + + class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import add_page + from .views import add_page return add_page(context, request) def test_it_notsubmitted(self): @@ -88,7 +88,7 @@ class AddPageTests(unittest.TestCase): self.assertEqual(info['save_url'], request.resource_url( context, 'add_page', 'AnotherPage')) - + def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, @@ -102,7 +102,7 @@ class AddPageTests(unittest.TestCase): class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import edit_page + from .views import edit_page return edit_page(context, request) def test_it_notsubmitted(self): @@ -112,7 +112,7 @@ class EditPageTests(unittest.TestCase): self.assertEqual(info['page'], context) self.assertEqual(info['save_url'], request.resource_url(context, 'edit_page')) - + def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py index a570410ca..2f0502c17 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py @@ -2,19 +2,26 @@ from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound + from pyramid.view import view_config -from pyramid.security import authenticated_userid -from tutorial.models import Page +from pyramid.security import ( + authenticated_userid, + remember, + forget, + ) + +from .security import USERS +from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='tutorial.models.Wiki', permission='view') +@view_config(context='.models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='tutorial.models.Page', +@view_config(context='.models.Page', renderer='templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ @@ -38,7 +45,7 @@ def view_page(context, request): return dict(page = context, content = content, edit_url = edit_url, logged_in = logged_in) -@view_config(name='add_page', context='tutorial.models.Wiki', +@view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', permission='edit') def add_page(context, request): @@ -59,7 +66,7 @@ def add_page(context, request): return dict(page = page, save_url = save_url, logged_in = logged_in) -@view_config(name='edit_page', context='tutorial.models.Page', +@view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt', permission='edit') def edit_page(context, request): @@ -72,4 +79,39 @@ def edit_page(context, request): return dict(page = context, save_url = request.resource_url(context, 'edit_page'), logged_in = logged_in) - + +@view_config(context='.models.Wiki', name='login', + renderer='templates/login.pt') +@view_config(context='pyramid.httpexceptions.HTTPForbidden', + renderer='templates/login.pt') +def login(request): + login_url = request.resource_url(request.context, '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(context='.models.Wiki', name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location = request.resource_url(request.context), + headers = headers) diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index e49a61129..b63933fc5 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -1,6 +1,6 @@ from pyramid.config import Configurator from pyramid_zodbconn import get_connection -from tutorial.models import appmaker +from .models import appmaker def root_factory(request): conn = get_connection(request) @@ -10,6 +10,6 @@ def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - config.scan('tutorial') + config.add_static_view('static', 'static', cache_max_age=3600) + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index f9f351c97..557e071ed 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <meta name="keywords" content="python web application" /> <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="shortcut icon" href="/static/favicon.ico" /> <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" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py index 1f3c3bb4d..8d2374be1 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py @@ -10,7 +10,7 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from tutorial.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'tutorial') diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py index 157b9ac8f..4265b6bf7 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py @@ -1,7 +1,6 @@ from pyramid.view import view_config -from tutorial.models import MyModel +from .models import MyModel -@view_config(context=MyModel, - renderer='tutorial:templates/mytemplate.pt') +@view_config(context=MyModel, renderer='templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index 2d637b9de..c59f36e7b 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -1,6 +1,6 @@ from pyramid.config import Configurator from pyramid_zodbconn import get_connection -from tutorial.models import appmaker +from .models import appmaker def root_factory(request): conn = get_connection(request) @@ -10,7 +10,7 @@ def main(global_config, **settings): """ This function returns a WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - config.scan('tutorial') + config.add_static_view('static', 'static', cache_max_age=3600) + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt index 14b88d16a..3597c679b 100644 --- a/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <meta name="keywords" content="python web application" /> <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="shortcut icon" href="/static/favicon.ico" /> <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" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/models/tutorial/tests.py b/docs/tutorials/wiki/src/models/tutorial/tests.py index 51c97a95d..9fd13a18d 100644 --- a/docs/tutorials/wiki/src/models/tutorial/tests.py +++ b/docs/tutorials/wiki/src/models/tutorial/tests.py @@ -5,7 +5,7 @@ from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Page + from .models import Page return Page def _makeOne(self, data=u'some data'): @@ -14,11 +14,11 @@ class PageModelTests(unittest.TestCase): def test_constructor(self): instance = self._makeOne() self.assertEqual(instance.data, u'some data') - + class WikiModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Wiki + from .models import Wiki return Wiki def _makeOne(self): @@ -32,7 +32,7 @@ class WikiModelTests(unittest.TestCase): class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): - from tutorial.models import appmaker + from .models import appmaker return appmaker(zodb_root) def test_no_app_root(self): @@ -55,7 +55,7 @@ class ViewTests(unittest.TestCase): testing.tearDown() def test_my_view(self): - from tutorial.views import my_view + from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'tutorial') diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py index 2346602c9..7c1f1d228 100644 --- a/docs/tutorials/wiki/src/models/tutorial/views.py +++ b/docs/tutorials/wiki/src/models/tutorial/views.py @@ -1,5 +1,5 @@ from pyramid.view import view_config -@view_config(renderer='tutorial:templates/mytemplate.pt') +@view_config(renderer='templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/tests/tutorial/__init__.py b/docs/tutorials/wiki/src/tests/tutorial/__init__.py index 2d6eb5ecb..20ee685ee 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/tests/tutorial/__init__.py @@ -4,8 +4,8 @@ from pyramid_zodbconn import get_connection from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy -from tutorial.models import appmaker -from tutorial.security import groupfinder +from .models import appmaker +from .security import groupfinder def root_factory(request): conn = get_connection(request) @@ -20,6 +20,6 @@ def main(global_config, **settings): config = Configurator(root_factory=root_factory, settings=settings, authentication_policy=authn_policy, authorization_policy=authz_policy) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) - config.scan('tutorial') + config.add_static_view('static', 'static', cache_max_age=3600) + config.scan() return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/tests/tutorial/login.py b/docs/tutorials/wiki/src/tests/tutorial/login.py deleted file mode 100644 index d608a7d0b..000000000 --- a/docs/tutorials/wiki/src/tests/tutorial/login.py +++ /dev/null @@ -1,44 +0,0 @@ -from pyramid.httpexceptions import HTTPFound - -from pyramid.security import remember -from pyramid.security import forget -from pyramid.view import view_config - -from tutorial.security import USERS - -@view_config(context='tutorial.models.Wiki', name='login', - renderer='templates/login.pt') -@view_config(context='pyramid.httpexceptions.HTTPForbidden', - renderer='templates/login.pt') -def login(request): - login_url = request.resource_url(request.context, '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(context='tutorial.models.Wiki', name='logout') -def logout(request): - headers = forget(request) - return HTTPFound(location = request.resource_url(request.context), - headers = headers) - diff --git a/docs/tutorials/wiki/src/tests/tutorial/security.py b/docs/tutorials/wiki/src/tests/tutorial/security.py index cfd13071e..d88c9c71f 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/security.py +++ b/docs/tutorials/wiki/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/wiki/src/tests/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt index f9da6c414..0d0738f7f 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt index 64e592ea9..2c7235761 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt index 14b88d16a..3597c679b 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <meta name="keywords" content="python web application" /> <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="shortcut icon" href="/static/favicon.ico" /> <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" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt index d207a0c23..9dd6540cf 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/tests/tutorial/tests.py b/docs/tutorials/wiki/src/tests/tutorial/tests.py index b1d4e68c3..81f7a1882 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/tests.py +++ b/docs/tutorials/wiki/src/tests/tutorial/tests.py @@ -5,7 +5,7 @@ from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Page + from .models import Page return Page def _makeOne(self, data=u'some data'): @@ -18,7 +18,7 @@ class PageModelTests(unittest.TestCase): class WikiModelTests(unittest.TestCase): def _getTargetClass(self): - from tutorial.models import Wiki + from .models import Wiki return Wiki def _makeOne(self): @@ -31,7 +31,7 @@ class WikiModelTests(unittest.TestCase): class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): - from tutorial.models import appmaker + from .models import appmaker return appmaker(zodb_root) def test_it(self): @@ -42,7 +42,7 @@ class AppmakerTests(unittest.TestCase): class ViewWikiTests(unittest.TestCase): def test_it(self): - from tutorial.views import view_wiki + from .views import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) @@ -50,7 +50,7 @@ class ViewWikiTests(unittest.TestCase): class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import view_page + from .views import view_page return view_page(context, request) def test_it(self): @@ -76,7 +76,7 @@ class ViewPageTests(unittest.TestCase): class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import add_page + from .views import add_page return add_page(context, request) def test_it_notsubmitted(self): @@ -102,7 +102,7 @@ class AddPageTests(unittest.TestCase): class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): - from tutorial.views import edit_page + from .views import edit_page return edit_page(context, request) def test_it_notsubmitted(self): @@ -133,7 +133,7 @@ class FunctionalTests(unittest.TestCase): def setUp(self): import tempfile import os.path - from tutorial import main + from . import main self.tmpdir = tempfile.mkdtemp() dbpath = os.path.join( self.tmpdir, 'test.db') diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py index a570410ca..2f0502c17 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/views.py +++ b/docs/tutorials/wiki/src/tests/tutorial/views.py @@ -2,19 +2,26 @@ from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound + from pyramid.view import view_config -from pyramid.security import authenticated_userid -from tutorial.models import Page +from pyramid.security import ( + authenticated_userid, + remember, + forget, + ) + +from .security import USERS +from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='tutorial.models.Wiki', permission='view') +@view_config(context='.models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='tutorial.models.Page', +@view_config(context='.models.Page', renderer='templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ @@ -38,7 +45,7 @@ def view_page(context, request): return dict(page = context, content = content, edit_url = edit_url, logged_in = logged_in) -@view_config(name='add_page', context='tutorial.models.Wiki', +@view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', permission='edit') def add_page(context, request): @@ -59,7 +66,7 @@ def add_page(context, request): return dict(page = page, save_url = save_url, logged_in = logged_in) -@view_config(name='edit_page', context='tutorial.models.Page', +@view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt', permission='edit') def edit_page(context, request): @@ -72,4 +79,39 @@ def edit_page(context, request): return dict(page = context, save_url = request.resource_url(context, 'edit_page'), logged_in = logged_in) - + +@view_config(context='.models.Wiki', name='login', + renderer='templates/login.pt') +@view_config(context='pyramid.httpexceptions.HTTPForbidden', + renderer='templates/login.pt') +def login(request): + login_url = request.resource_url(request.context, '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(context='.models.Wiki', name='logout') +def logout(request): + headers = forget(request) + return HTTPFound(location = request.resource_url(request.context), + headers = headers) diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index 009013b3f..957a0b705 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -10,6 +10,6 @@ def main(global_config, **settings): """ This function returns a WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) - config.add_static_view('static', 'tutorial:static', cache_max_age=3600) + config.add_static_view('static', 'static', cache_max_age=3600) config.scan('tutorial') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt index 6dbb0edde..24ed2e592 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt index 14b88d16a..3597c679b 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt @@ -5,19 +5,19 @@ <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <meta name="keywords" content="python web application" /> <meta name="description" content="pyramid web application" /> - <link rel="shortcut icon" href="${request.static_url('tutorial:static/favicon.ico')}" /> + <link rel="shortcut icon" href="/static/favicon.ico" /> <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" /> + <link rel="stylesheet" href="/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" /> + <link rel="stylesheet" href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> <body> <div id="wrap"> <div id="top"> <div class="top align-center"> - <div><img src="${request.static_url('tutorial:static/pyramid.png')}" width="750" height="169" alt="pyramid"/></div> + <div><img src="/static/pyramid.png" width="750" height="169" alt="pyramid"/></div> </div> </div> <div id="middle"> diff --git a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt index 537ae3a15..424c4302a 100644 --- a/docs/tutorials/wiki/src/views/tutorial/templates/view.pt +++ b/docs/tutorials/wiki/src/views/tutorial/templates/view.pt @@ -9,13 +9,13 @@ <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')}" /> + href="/static/favicon.ico" /> <link rel="stylesheet" - href="${request.static_url('tutorial:static/pylons.css')}" + href="/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')}" + href="/static/ie6.css" type="text/css" media="screen" charset="utf-8" /> <![endif]--> </head> @@ -25,7 +25,7 @@ <div class="top-small align-center"> <div> <img width="220" height="50" alt="pyramid" - src="${request.static_url('tutorial:static/pyramid-small.png')}" /> + src="/static/pyramid-small.png" /> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py index 245cda682..016f5b6bb 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views.py +++ b/docs/tutorials/wiki/src/views/tutorial/views.py @@ -4,17 +4,16 @@ import re from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config -from tutorial.models import Page +from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='tutorial.models.Wiki') +@view_config(context='.models.Wiki') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='tutorial.models.Page', - renderer='tutorial:templates/view.pt') +@view_config(context='.models.Page', renderer='templates/view.pt') def view_page(context, request): wiki = context.__parent__ @@ -33,8 +32,8 @@ def view_page(context, request): edit_url = request.resource_url(context, 'edit_page') return dict(page = context, content = content, edit_url = edit_url) -@view_config(name='add_page', context='tutorial.models.Wiki', - renderer='tutorial:templates/edit.pt') +@view_config(name='add_page', context='.models.Wiki', + renderer='templates/edit.pt') def add_page(context, request): name = request.subpath[0] if 'form.submitted' in request.params: @@ -50,8 +49,8 @@ def add_page(context, request): page.__parent__ = context return dict(page = page, save_url = save_url) -@view_config(name='edit_page', context='tutorial.models.Page', - renderer='tutorial:templates/edit.pt') +@view_config(name='edit_page', context='.models.Page', + renderer='templates/edit.pt') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] @@ -59,5 +58,3 @@ def edit_page(context, request): return dict(page = context, save_url = request.resource_url(context, 'edit_page')) - - diff --git a/docs/tutorials/wiki/tests.rst b/docs/tutorials/wiki/tests.rst index 841baa8d1..1ddb8f408 100644 --- a/docs/tutorials/wiki/tests.rst +++ b/docs/tutorials/wiki/tests.rst @@ -6,22 +6,21 @@ 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 -================== +Test the Models +=============== -We write tests for the model -classes and the appmaker. Changing ``tests.py``, we'll write a separate test -class for each model class, and we'll write a test class for the -``appmaker``. +We write tests for the model classes and the appmaker. Changing +``tests.py``, we'll write a separate test class for each model class, and +we'll write a test class for the ``appmaker``. To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a -result of the ``pyramid_zodb`` project generator. We'll add three test +result of the ``zodb`` project generator. We'll add three test classes: one for the ``Page`` model named ``PageModelTests``, one for the ``Wiki`` model named ``WikiModelTests``, and one for the appmaker named ``AppmakerTests``. -Testing the Views -================= +Test 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, @@ -38,8 +37,8 @@ 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`` -==================================================== +View 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: @@ -48,8 +47,8 @@ below: :linenos: :language: python -Running the Tests -================= +Run the Tests +============= We can run these tests by using ``setup.py test`` in the same way we did in :ref:`running_tests`. However, first we must edit our ``setup.py`` to 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 8dc886373..77658970d 100644 --- a/docs/tutorials/wiki2/basiclayout.rst +++ b/docs/tutorials/wiki2/basiclayout.rst @@ -2,82 +2,96 @@ Basic Layout ============ -The starter files generated by the ``pyramid_routesalchemy`` scaffold are -basic, but they provide a good orientation for the high-level patterns common -to most :term:`url dispatch` -based :app:`Pyramid` projects. +The starter files generated by the ``alchemy`` scaffold are very basic, but +they provide a good orientation for the high-level patterns common to most +:term:`url dispatch` -based :app:`Pyramid` projects. The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/basiclayout/ <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/basiclayout/>`_. -App Startup with ``__init__.py`` --------------------------------- +Application Configuration with ``__init__.py`` +---------------------------------------------- A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file. Even if empty, this marks a directory as a Python -package. We use ``__init__.py`` both as a package marker and to contain -configuration code. +package. We use ``__init__.py`` both as a marker indicating the directory +it's contained within is a package, and to contain configuration code. Our +``__init__.py`` file will look like this: -The generated ``development.ini`` file is read by ``pserve`` which looks for -the application module in the ``use`` variable of the ``app:main`` -section. The *entry point* is defined in the Setuptools configuration of this -module, specifically in the ``setup.py`` file. For this tutorial, the *entry -point* is defined as ``tutorial:main`` and points to a function named -``main``. + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :linenos: + :language: py -First we need some imports to support later code: +Let's go over this piece-by-piece. First, we need some imports to support +later code: .. literalinclude:: src/basiclayout/tutorial/__init__.py :end-before: main :linenos: :language: py -Next we define the main function and create a SQLAlchemy database engine from -the ``sqlalchemy.`` prefixed settings in the ``development.ini`` file's -``[app:main]`` section. This will be a URI (something like -``sqlite://``): +``__init__.py`` defines a function named ``main``. Here is the entirety of +the ``main`` function we've defined in our ``__init__.py``: + + .. literalinclude:: src/basiclayout/tutorial/__init__.py + :pyobject: main + :linenos: + :language: py + +When you invoke the ``pserve development.ini`` command, the ``main`` function +above is executed. It accepts some settings and returns a :term:`WSGI` +application. You can read :ref:`startup_chapter` for details about *how* +this function is found and called when you run ``pserve``, but for purposes +of brevity, we'll elide the details here. + +The main function first creates a SQLAlchemy database engine using +``engine_from_config`` from the ``sqlalchemy.`` prefixed settings in the +``development.ini`` file's ``[app:main]`` section. This will be a URI +(something like ``sqlite://``): .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 6-9 + :lines: 9 :linenos: :language: py -We then initialize our SQL database using SQLAlchemy, passing -it the engine: +``main`` then initializes our SQL database using SQLAlchemy, passing it the +engine: .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 10 :language: py -The next step is to construct a :term:`Configurator`: +The next step of ``main`` is to construct a :term:`Configurator` object: .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 11 :language: py ``settings`` is passed to the Configurator as a keyword argument with the -dictionary values passed by PasteDeploy as the ``**settings`` argument. This -will be a dictionary of settings parsed from the ``.ini`` file, which -contains deployment-related values such as ``pyramid.reload_templates``, +dictionary values passed as the ``**settings`` argument. This will be a +dictionary of settings parsed from the ``.ini`` file, which contains +deployment-related values such as ``pyramid.reload_templates``, ``db_string``, etc. -We now can call :meth:`pyramid.config.Configurator.add_static_view` with the -arguments ``static`` (the name), and ``tutorial:static`` (the path): +``'main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with +two arguments: ``static`` (the name), and ``static`` (the path): .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 12 :language: py -This registers a static resource view which will match any URL that starts with -``/static/``. This will serve up static resources for us from within the -``static`` directory of our ``tutorial`` package, in this case, -via ``http://localhost:6543/static/`` and below. With this declaration, -we're saying that any URL that starts with ``/static`` should go to the -static view; any remainder of its path (e.g. the ``/foo`` in -``/static/foo``) will be used to compose a path to a static file resource, -such as a CSS file. +This registers a static resource view which will match any URL that starts +with the prefix ``/static`` (by virtue of the first argument to add_static +view). This will serve up static resources for us from within the ``static`` +directory of our ``tutorial`` package, in this case, via +``http://localhost:6543/static/`` and below (by virtue of the second argument +to add_static_view). With this declaration, we're saying that any URL that +starts with ``/static`` should go to the static view; any remainder of its +path (e.g. the ``/foo`` in ``/static/foo``) will be used to compose a path to +a static file resource, such as a CSS file. -Using the configurator we can also register a :term:`route configuration` +Using the configurator ``main`` also registers a :term:`route configuration` via the :meth:`pyramid.config.Configurator.add_route` method that will be used when the URL is ``/``: @@ -88,44 +102,77 @@ used when the URL is ``/``: Since this route has a ``pattern`` equalling ``/`` it is the route that will be matched when the URL ``/`` is visted, e.g. ``http://localhost:6543/``. -Mapping the ``home`` route to code is done by registering a view. You will -use :meth:`pyramid.config.Configurator.add_view` in :term:`URL dispatch` to -register views for the routes, mapping your patterns to code: +``main`` next calls the ``scan`` method of the configurator, which will +recursively scan our ``tutorial`` package, looking for ``@view_config`` (and +other special) decorators. When it finds a ``@view_config`` decorator, a +view configuration will be registered, which will allow one of our +application URLs to be mapped to some code. .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 14-15 + :lines: 14 :language: py -The first positional ``add_view`` argument ``tutorial.views.my_view`` is the -dotted name to a *function* we write (generated by the -``pyramid_routesalchemy`` scaffold) that is given a ``request`` object and -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. - -Finally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app` -method to return a :term:`WSGI` application: +Finally, ``main`` is finished configuring things, so it uses the +:meth:`pyramid.config.Configurator.make_wsgi_app` method to return a +:term:`WSGI` application: .. literalinclude:: src/basiclayout/tutorial/__init__.py - :lines: 16 + :lines: 15 :language: py -Our final ``__init__.py`` file will look like this: +View Declarations via ``views.py`` +---------------------------------- - .. literalinclude:: src/basiclayout/tutorial/__init__.py +Mapping a :term:`route` to code that will be executed when that route's +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 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 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 +``my_view`` will be executed. The the function named ``my_view`` returns a +dictionary; the renderer will use the ``templates/mytemplate.pt`` template to +create a response based on the values in the dictionary. + +Note that the decorated function named ``my_view`` accepts a single argument +named ``request``. This is the standard call signature for a Pyramid +:term:`view callable`. + +Remember in our ``__init__.py`` when we executed the +:meth:`pyramid.config.Configurator.scan` method, e.g. ``config.scan()``? The +purpose of calling the scan method was to find and process this +``@view_config`` decorator in order to create a view configuration within our +application. Without being processed by ``scan``, the decorator effectively +does nothing. ``@view_config`` is inert without being detected via a +:term:`scan`. + Content Models with ``models.py`` --------------------------------- -In a SQLAlchemy-based application, a *model* object is an object -composed by querying the SQL database which backs an application. -SQLAlchemy is an "object relational mapper" (an ORM). The -``models.py`` file is where the ``pyramid_routesalchemy`` scaffold -put the classes that implement our models. +In a SQLAlchemy-based application, a *model* object is an object composed by +querying the SQL database. The ``models.py`` file is where the ``alchemy`` +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. @@ -137,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 @@ -161,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 083ec0aa8..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. +Changing ``scripts/populate.py`` +-------------------------------- -.. literalinclude:: src/models/tutorial/models.py - :pyobject: initialize_sql - :linenos: - :language: python +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). -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``. - -Our ``DBSession`` assignment stays the same as the original generated -``models.py``. - -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,6 +70,53 @@ something like this: :linenos: :language: python +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 + + 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 f9f5c4fba..be97f1cb3 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -40,13 +40,6 @@ Preparation, UNIX $ bin/easy_install pyramid -#. Use ``easy_install`` to install various packages from PyPI. - - .. code-block:: text - - $ bin/easy_install docutils nose coverage zope.sqlalchemy \ - SQLAlchemy pyramid_tm - Preparation, Windows -------------------- @@ -69,14 +62,6 @@ Preparation, Windows c:\pyramidtut> Scripts\easy_install pyramid -#. Use ``easy_install`` to install various packages from PyPI. - - .. code-block:: text - - c:\pyramidtut> Scripts\easy_install docutils \ - nose coverage zope.sqlalchemy SQLAlchemy pyramid_tm - - .. _sql_making_a_project: Making a Project @@ -84,7 +69,7 @@ Making a Project Your next step is to create a project. :app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We will use the -``pyramid_routesalchemy`` scaffold, which generates an application +``alchemy`` scaffold, which generates an application that uses :term:`SQLAlchemy` and :term:`URL dispatch`. The below instructions assume your current working directory is the @@ -94,20 +79,27 @@ On UNIX: .. code-block:: text - $ bin/pcreate -s routesalchemy tutorial + $ bin/pcreate -s alchemy tutorial On Windows: .. code-block:: text - c:\pyramidtut> Scripts\pcreate -s routesalchemy tutorial + c:\pyramidtut> Scripts\pcreate -s alchemy tutorial -.. note:: If you are using Windows, the ``pyramid_routesalchemy`` +.. note:: If you are using Windows, the ``alchemy`` scaffold may not deal gracefully with installation into a location that contains spaces in the path. If you experience startup problems, try putting both the virtualenv and the project into directories that do not contain spaces in their paths. +Success executing this command will end with a line to the console something +like:: + + Please run the "populate_tutorial" script to set up the SQL + database before starting the application (e.g. + "$myvirtualenv/bin/populate_tutorial development.ini".) + Installing the Project in "Development Mode" ============================================ @@ -131,6 +123,11 @@ On Windows: 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 + .. _sql_running_tests: Running the Tests @@ -151,22 +148,13 @@ On Windows: c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q -Starting the Application -======================== - -Start the application. +For a successful test run, you should see output like this:: -On UNIX: - -.. code-block:: text - - $ ../bin/pserve development.ini --reload - -On Windows: - -.. code-block:: text - - c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload + . + ---------------------------------------------------------------------- + Ran 1 test in 0.094s + + OK Exposing Test Coverage Information ================================== @@ -208,24 +196,142 @@ On Windows: c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^ --cover-erase --with-coverage -Looks like our package's ``models`` module doesn't quite have 100% -test coverage. +If successful, you will see output something like this:: -Visit the Application in a Browser -================================== + . + Name Stmts Miss Cover Missing + ------------------------------------------------ + tutorial 11 7 36% 9-15 + tutorial.models 17 0 100% + tutorial.scripts 0 0 100% + tutorial.tests 24 0 100% + tutorial.views 6 0 100% + ------------------------------------------------ + TOTAL 58 7 88% + ---------------------------------------------------------------------- + Ran 1 test in 0.459s + + OK + +Looks like our package doesn't quite have 100% test coverage. + +Starting the Application +======================== + +Start the application. + +On UNIX: + +.. code-block:: text + + $ ../bin/pserve development.ini --reload + +On Windows: + +.. code-block:: text + + c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload + +If successful, you will see something like this on your console:: + + Starting subprocess with file monitor + Starting server in PID 8966. + Starting HTTP server on http://0.0.0.0:6543 + +This means the server is ready to accept requests. + +Populating the Database +======================= + +In a web browser, visit ``http://localhost:6543/``. + +You will see an error page with a title something like this:: + + sqlalchemy.exc.OperationalError + + OperationalError: (OperationalError) no such table: models ... + +Oh no! Something isn't working! + +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. + +Stop the running Pyramid application by pressing ``ctrl-C`` in the console. +Make sure you're still in the ``tutorial`` directory (the directory with a +``development.ini`` in it) and type the following command: + +On UNIX: + +.. code-block:: text + + $ ../bin/populate_tutorial development.ini + +On Windows: + +.. code-block:: text + + c:\pyramidtut\tutorial> ..\Scripts\populate_tutorial development.ini + +The output to your console should be something like this:: + + 2011-11-26 14:42:25,012 INFO [sqlalchemy.engine.base.Engine][MainThread] + PRAGMA table_info("models") + 2011-11-26 14:42:25,013 INFO [sqlalchemy.engine.base.Engine][MainThread] () + 2011-11-26 14:42:25,013 INFO [sqlalchemy.engine.base.Engine][MainThread] + CREATE TABLE models ( + id INTEGER NOT NULL, + name VARCHAR(255), + value INTEGER, + PRIMARY KEY (id), + UNIQUE (name) + ) + 2011-11-26 14:42:25,013 INFO [sqlalchemy.engine.base.Engine][MainThread] () + 2011-11-26 14:42:25,135 INFO [sqlalchemy.engine.base.Engine][MainThread] + COMMIT + 2011-11-26 14:42:25,137 INFO [sqlalchemy.engine.base.Engine][MainThread] + BEGIN (implicit) + 2011-11-26 14:42:25,138 INFO [sqlalchemy.engine.base.Engine][MainThread] + INSERT INTO models (name, value) VALUES (?, ?) + 2011-11-26 14:42:25,139 INFO [sqlalchemy.engine.base.Engine][MainThread] + (u'one', 1) + 2011-11-26 14:42:25,140 INFO [sqlalchemy.engine.base.Engine][MainThread] + COMMIT + +Success! You should now have a ``tutorial.db`` file in your current working +directory. This will be a SQLite database with a single table defined in it +(``models``). + +Starting the Application (Again) +================================ + +Start the application again. + +On UNIX: + +.. code-block:: text + + $ ../bin/pserve development.ini --reload + +On Windows: + +.. code-block:: text + + c:\pyramidtut\tutorial> ..\Scripts\pserve development.ini --reload -In a browser, visit ``http://localhost:6543/``. You will see the -generated application's default page. +At this point, when you visit ``http://localhost:6543/`` in your web browser, +you will no longer see an error; instead you will see the generated +application's default page. One thing you'll notice is the "debug toolbar" icon on right hand side of the page. You can read more about the purpose of the icon at :ref:`debug_toolbar`. It allows you to get information about your application while you develop. -Decisions the ``pyramid_routesalchemy`` Scaffold Has Made For You +Decisions the ``alchemy`` Scaffold Has Made For You ================================================================= -Creating a project using the ``pyramid_routesalchemy`` scaffold makes +Creating a project using the ``alchemy`` scaffold makes the following assumptions: - you are willing to use :term:`SQLAlchemy` as a database access tool 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 b4038de3c..253341563 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py @@ -1,18 +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 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/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 e550e3257..3e6abf2c2 100644 --- a/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki2/src/basiclayout/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/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 c120b1c61..25cf2c25e 100644 --- a/docs/tutorials/wiki2/tests.rst +++ b/docs/tutorials/wiki2/tests.rst @@ -13,9 +13,9 @@ 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``. +result of the ``alchemy`` scaffold. We'll add two test classes: one for the +``Page`` model named ``PageModelTests``, and one for the ``populate`` script +named ``Test_populate``. Testing the Views ================= |
