docs/tutorials/wiki2/authorization.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/basiclayout.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/definingmodels.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/definingviews.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/design.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/distributing.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/installation.rst | ●●●●● patch | view | raw | blame | history | |
docs/tutorials/wiki2/tests.rst | ●●●●● patch | view | raw | blame | history |
docs/tutorials/wiki2/authorization.rst
@@ -7,8 +7,8 @@ :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`) the server to view, edit, and add pages to our wiki. We'll change that to allow only people who 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. @@ -41,13 +41,12 @@ :language: python We're going to start to use a custom :term:`root factory` within our ``__init__.py`` file. The objects generated by the root factory will be used as the :term:`context` of each request to our application. We do this to allow :app:`Pyramid` declarative security to work properly. The context object generated by the root factory during a request will be decorated with security declarations. When we begin to use a custom root factory to generate our contexts, we can begin to make use of the declarative security features of :app:`Pyramid`. ``__init__.py`` file. The objects generated by the root factory will be used as the :term:`context` of each request to our application. Those context objects will be decorated with security declarations. When we use a custom root factory to generate our contexts, we can begin to make use of the declarative security features of :app:`Pyramid`. We'll modify our ``__init__.py``, passing in a :term:`root factory` to our :term:`Configurator` constructor. We'll point it at the new class we created @@ -65,10 +64,12 @@ list during view callable execution. See :ref:`assigning_acls` for more information about what an :term:`ACL` represents. .. note: Although we don't use the functionality here, the ``factory`` used to create route contexts may differ per-route as opposed to globally. See the ``factory`` argument to :meth:`pyramid.config.Configurator.add_route` for more info. .. note:: Although we don't use the functionality here, the ``factory`` used to create route contexts may differ per-route as opposed to globally. See the ``factory`` argument to :meth:`pyramid.config.Configurator.add_route` for more info. We'll pass the ``RootFactory`` we created in the step above in as the ``root_factory`` argument to a :term:`Configurator`. @@ -147,9 +148,10 @@ mapping him to this group in the ``GROUPS`` data structure (``GROUPS = {'editor':['group:editors']}``). Since the ``groupfinder`` function consults the ``GROUPS`` data structure, this will mean that, as a result of the ACL attached to the root returned by the root factory, and the permission associated with the ``add_page`` and ``edit_page`` views, the ``editor`` user should be able to add and edit pages. result of the ACL attached to the :term:`context` object returned by the root factory, and the permission associated with the ``add_page`` and ``edit_page`` views, the ``editor`` user should be able to add and edit pages. Adding Login and Logout Views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -176,20 +178,20 @@ :language: python The ``login`` view callable is decorated with two decorators, a ``@view_config`` decorators, which associates it with the ``login`` route, the other a ``@forbidden_view_config`` decorator which turns it in to an :term:`exception view` when Pyramid raises a :class:`pyramid.httpexceptions.HTTPForbidden` exception. The one which associates it with the ``login`` route makes it visible when we visit ``/login``. The other one makes it a :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. ``@view_config`` decorator, which associates it with the ``login`` route, and a ``@forbidden_view_config`` decorator which turns it in to an :term:`exception view`. The one which associates it with the ``login`` route makes it visible when we visit ``/login``. The other one makes it a :term:`forbidden view`. The forbidden view is displayed whenever Pyramid or your application raises an :class:`pyramid.httpexceptions.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``. visit ``/logout``. We'll need to import some stuff to service the needs of these two functions: the ``pyramid.view.forbidden_view_config`` class, a number of values from the @@ -222,8 +224,8 @@ Return a logged_in flag to the renderer --------------------------------------- We'll then change the return value of these views to pass the `resulting `logged_in`` value to the template, e.g.: We'll then change the return value of these views to pass the resulting ``logged_in`` value to the template, e.g.: .. code-block:: python :linenos: @@ -248,12 +250,14 @@ 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 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`` :term:`principal` the ``edit`` permission in the :term:`root factory` via its ACL, so only a user who is a member of the group named ``group:editors`` will be able to invoke the views associated with the ``add_page`` or ``edit_page`` routes. Adding the ``login.pt`` Template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -322,7 +326,7 @@ - ``http://localhost:6543/FrontPage`` invokes the ``view_page`` view of the FrontPage page object. - ``http://localhost:6543/edit_page/FrontPage`` - ``http://localhost:6543/FrontPage/edit_page`` invokes the edit view for the FrontPage object. It is executable by only the ``editor`` user. If a different user (or the anonymous user) invokes it, a login form will be displayed. Supplying the docs/tutorials/wiki2/basiclayout.rst
@@ -74,7 +74,7 @@ deployment-related values such as ``pyramid.reload_templates``, ``db_string``, etc. ``'main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with ``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 @@ -123,10 +123,11 @@ View Declarations via ``views.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. Mapping a :term:`route` to code that will be executed when a match for the route's pattern occurs 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. Open ``tutorial/tutorial/views.py``. It should already contain the following: @@ -151,7 +152,7 @@ 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 :meth:`pyramid.config.Configurator.scan` method, i.e. ``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 @@ -199,7 +200,7 @@ :linenos: :language: py Our sample model has an ``__init__`` that takes a two arguments (``name``, Our example model has an ``__init__`` that takes a two arguments (``name``, and ``value``). It stores these values as ``self.name`` and ``self.value`` within the ``__init__`` function itself. The ``MyModel`` class also has a ``__tablename__`` attribute. This informs SQLAlchemy which table to use to docs/tutorials/wiki2/definingmodels.rst
@@ -57,7 +57,7 @@ Changing ``scripts/initializedb.py`` ------------------------------------ We haven't looked at the guts of this file yet, but within the ``scripts`` We haven't looked at the details of this file yet, but within the ``scripts`` directory of your ``tutorial`` package is a file named ``initializedb.py``. Code in this file is executed whenever we run the ``initialize_tutorial_db`` command (as we did in the installation step of this tutorial). docs/tutorials/wiki2/definingviews.rst
@@ -157,17 +157,19 @@ object) is a cue to :app:`Pyramid` that it should try to use a :term:`renderer` associated with the view configuration to render a template. In our case, the template which will be rendered will be the ``templates/view.pt`` template, as per the configuration put into effect in ``__init__.py``. template, as indicated in the ``@view_config`` decorator that is applied to ``view_page()``. The ``add_page`` view function ------------------------------ ``add_page()`` is invoked when a user clicks on a *WikiWord* which isn't yet represented as a page in the system. The ``check`` function within the ``view_page`` view generates URLs to this view. It also acts as a handler for the form that is generated when we want to add a page object. The ``matchdict`` attribute of the request passed to the ``add_page()`` view will have the values we need to construct URLs and find model objects. ``add_page()`` is invoked when a user clicks on a *WikiWord* which isn't yet represented as a page in the system. The ``check`` function within the ``view_page`` view generates URLs to this view. ``add_page()`` also acts as a handler for the form that is generated when we want to add a page object. The ``matchdict`` attribute of the request passed to the ``add_page()`` view will have the values we need to construct URLs and find model objects. .. literalinclude:: src/views/tutorial/views.py :lines: 45-56 @@ -179,17 +181,17 @@ e.g. ``http://localhost:6543/add_page/SomeName``, the value for ``'pagename'`` in the ``matchdict`` will be ``'SomeName'``. If the view execution is *not* a result of a form submission (if the If the view execution is *not* a result of a form submission (i.e. the expression ``'form.submitted' in request.params`` is ``False``), the view callable renders a template. To do so, it generates a "save url" which the template uses as the form post URL during rendering. We're lazy here, so we're trying to use the same template (``templates/edit.pt``) for the add view as well as the page edit view, so we create a dummy Page object in order to satisfy the edit form's desire to have *some* page object exposed as ``page``, and :app:`Pyramid` will render the template associated with this view to a response. we're going to use the same template (``templates/edit.pt``) for the add view as well as the page edit view. To do so we create a dummy Page object in order to satisfy the edit form's desire to have *some* page object exposed as ``page``. :app:`Pyramid` will render the template associated with this view to a response. If the view execution *is* a result of a form submission (if the expression If the view execution *is* a result of a form submission (i.e. the expression ``'form.submitted' in request.params`` is ``True``), we scrape the page body from the form data, create a Page object with this page body and the name taken from ``matchdict['pagename']``, and save it into the database using @@ -210,12 +212,12 @@ :linenos: :language: python If the view execution is *not* a result of a form submission (if the If the view execution is *not* a result of a form submission (i.e. the expression ``'form.submitted' in request.params`` is ``False``), the view simply renders the edit form, passing the page object and a ``save_url`` which will be used as the action of the generated form. If the view execution *is* a result of a form submission (if the expression If the view execution *is* a result of a form submission (i.e. the expression ``'form.submitted' in request.params`` is ``True``), the view grabs the ``body`` element of the request parameters and sets it as the ``data`` attribute of the page object. It then redirects to the ``view_page`` view @@ -231,11 +233,12 @@ The ``view.pt`` Template ------------------------ The ``view.pt`` template is used for viewing a single wiki page. It is used by the ``view_page`` view function. It should have a div that is "structure replaced" with the ``content`` value provided by the view. It should also have a link on the rendered page that points at the "edit" URL (the URL which invokes the ``edit_page`` view for the page being viewed). The ``view.pt`` template is used for viewing a single wiki page. It is used by the ``view_page`` view function. It should have a ``div`` that is "structure replaced" with the ``content`` value provided by the view. It should also have a link on the rendered page that points at the "edit" URL (the URL which invokes the ``edit_page`` view for the page being viewed). Once we're done with the ``view.pt`` template, it will look a lot like the below: @@ -323,11 +326,14 @@ ``route_name='edit_page'``. As a result of our edits, the ``__init__.py`` file should look something like so: something like: .. literalinclude:: src/views/tutorial/__init__.py :linenos: :language: python :emphasize-lines: 13-16 (The highlighted lines are the ones that need to be added or edited.) Viewing the Application in a Browser ==================================== docs/tutorials/wiki2/design.rst
@@ -4,7 +4,7 @@ Following is a quick overview of our wiki application, to help us understand the changes that we will be doing next in our default files generated by the paster scafffold. default files generated by the ``alchemy`` scaffold. Overall ------- @@ -34,9 +34,10 @@ Views ----- There will be four views to handle the normal operations of viewing, editing and adding wiki pages. Two additional views will handle the login and logout tasks related to security. There will be four views to handle the normal operations of adding and editing wiki pages, and viewing pages and the wiki front page. Two additional views will handle the login and logout tasks related to security. Security -------- @@ -59,7 +60,8 @@ | Allow | group:editors | Edit | +----------+----------------+----------------+ - Permission declarations for the views. - Permission declarations are added to the views to assert the security policies as each request is handled. Summary docs/tutorials/wiki2/distributing.rst
@@ -27,12 +27,12 @@ running sdist # ... more output ... creating dist tar -cf dist/tutorial-0.1.tar tutorial-0.1 gzip -f9 dist/tutorial-0.1.tar removing 'tutorial-0.1' (and everything under it) tar -cf dist/tutorial-0.0.tar tutorial-0.0 gzip -f9 dist/tutorial-0.0.tar removing 'tutorial-0.0' (and everything under it) Note that this command creates a tarball in the "dist" subdirectory named ``tutorial-0.1.tar.gz``. You can send this file to your friends named ``tutorial-0.0.tar.gz``. You can send this file to your friends to show them your cool new application. They should be able to install it by pointing the ``easy_install`` command directly at it. Or you can upload it to `PyPI <http://pypi.python.org>`_ and share it docs/tutorials/wiki2/installation.rst
@@ -141,7 +141,7 @@ c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q For a successful test run, you should see output like this:: For a successful test run, you should see output that ends like this:: . ---------------------------------------------------------------------- docs/tutorials/wiki2/tests.rst
@@ -4,31 +4,30 @@ 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. that it continues to work after changes are made in the future. Testing the Models ================== We write a test class for the model class ``Page`` and another test class for the ``initialize_sql`` function. To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a result of the ``alchemy`` scaffold. We'll add a test class named ``PageModelTests`` for the ``Page`` model. To test the model class ``Page`` we'll add a new ``PageModelTests`` class to our ``tests.py`` file that was generated as part of the ``alchemy`` scaffold. Testing the Views ================= We'll modify our ``tests.py`` file, adding tests for each view function we added above. As a result, we'll *delete* the ``ViewTests`` test in the file, and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views respectively. We'll modify our ``tests.py`` file, adding tests for each view function we added above. As a result, we'll *delete* the ``ViewTests`` class that the ``alchemy`` scaffold provided, and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views respectively. Functional tests ================ We test the whole application, covering security aspects that are not We'll test the whole application, covering security aspects that are not tested in the unit tests, like logging in, logging out, checking that the ``viewer`` user cannot add or edit pages, but the ``editor`` user can, and so on. @@ -36,8 +35,7 @@ Viewing the results of all our edits to ``tests.py`` ==================================================== Once we're done with the ``tests.py`` module, it will look a lot like the below: Once we're done with the ``tests.py`` module, it will look a lot like: .. literalinclude:: src/tests/tutorial/tests.py :linenos: @@ -55,6 +53,7 @@ :linenos: :language: python :lines: 9-20 :emphasize-lines: 10 After we've added a dependency on WebTest in ``setup.py``, we need to rerun ``setup.py develop`` to get WebTest installed into our virtualenv. Assuming @@ -88,12 +87,12 @@ c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q The expected result looks something like: The expected result ends something like: .. code-block:: text ...................... ---------------------------------------------------------------------- Ran 22 tests in 2.700s Ran 21 tests in 2.700s OK