diff options
Diffstat (limited to 'docs/tutorials/wiki')
46 files changed, 790 insertions, 1611 deletions
diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index 062c553b5..1b66ace96 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -2,14 +2,13 @@ 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 ``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 *authorization* and -*authentication*. 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. For purposes of demonstration we'll change +our application to allow people whom 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 *authorization* and *authentication*. We'll make use of both +features to provide security to our application. The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/authorization/ @@ -19,33 +18,29 @@ The source code for this tutorial stage can be browsed via Configuring a ``pyramid`` Authentication Policy -------------------------------------------------- -For any :app:`Pyramid` application to perform authorization, we -need to add a ``security.py`` module and we'll need to change our -:term:`application registry` to add an :term:`authentication policy` -and a :term:`authorization policy`. +For any :app:`Pyramid` application to perform authorization, we need to add a +``security.py`` module and we'll need to change our :term:`application +registry` to add an :term:`authentication policy` and a :term:`authorization +policy`. -Changing ``configure.zcml`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Adding Authentication and Authorization Policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We'll change our ``configure.zcml`` file to enable an -``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to -enable declarative security checking. We'll also add a new view -stanza, which specifies a :term:`forbidden view`. This configures our -login view to show up when :app:`Pyramid` detects that a view -invocation can not be authorized. When you're done, your -``configure.zcml`` will look like so: +We'll change our package's ``__init__.py`` file to enable an +``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable +declarative security checking. When you're done, your ``__init__.py`` will +look like so: -.. literalinclude:: src/authorization/tutorial/configure.zcml +.. literalinclude:: src/authorization/tutorial/__init__.py :linenos: - :language: xml + :language: python -Note that the ``authtktauthenticationpolicy`` tag has two attributes: -``secret`` and ``callback``. ``secret`` is a string representing an -encryption key used by the "authentication ticket" machinery -represented by this policy: it is required. The ``callback`` is a -string, representing a :term:`dotted Python name`, which points at the -``groupfinder`` function in the current directory's ``security.py`` -file. We haven't added that module yet, but we're about to. +Note that the creation of an ``AuthTktAuthenticationPolicy`` requires two +arguments: ``secret`` and ``callback``. ``secret`` is a string representing +an encryption key used by the "authentication ticket" machinery represented +by this policy: it is required. The ``callback`` is a reference to a +``groupfinder`` function in the ``tutorial`` package's ``security.py`` file. +We haven't added that module yet, but we're about to. Adding ``security.py`` ~~~~~~~~~~~~~~~~~~~~~~ @@ -59,14 +54,13 @@ content: :language: python The ``groupfinder`` function defined here is an authorization policy -"callback"; it is a callable that accepts a userid and a request. If -the userid exists in the set of users known by 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 this 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 +"callback"; it is a callable that accepts a userid and a request. If the +userid exists in the set of users known by 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 this 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). @@ -88,6 +82,26 @@ and logout views. Add a file named ``login.py`` to your application :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. + +The first view configuration decorator configures the ``login`` view callable +so it will be invoked when someone visits ``/login`` (when the context is a +Wiki and the view name is ``login``). The second decorator (with context of +``pyramid.exceptions.Forbidden``) specifies a :term:`forbidden view`. This +configures our login view to be presented to the user when :app:`Pyramid` +detects that a view invocation can not be authorized. Because we've +configured a forbidden view, the ``login`` view callable will be invoked +whenever one of our users tries to execute a view callable that they are not +allowed to invoke as determined by the :term:`authorization policy` in use. +In our application, for example, this means that if a user has not logged in, +and he tries to add or edit a Wiki page, he will be shown the 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 ~~~~~~~~~~~~~~~~~~~~~~~ @@ -142,17 +156,15 @@ class="main_content">`` div: <a href="${request.application_url}/logout">Logout</a> </span> -Giving Our Root Model Object an ACL ------------------------------------ +Giving Our Root Resource an ACL +------------------------------- -We need to give our root model object an :term:`ACL`. This ACL will -be sufficient to provide enough information to the :app:`Pyramid` -security machinery to challenge a user who doesn't have appropriate -credentials when he attempts to invoke the ``add_page`` or -``edit_page`` views. +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 +machinery to challenge a user who doesn't have appropriate credentials when +he attempts to invoke the ``add_page`` or ``edit_page`` views. -We need to perform some imports at module scope in our ``models.py`` -file: +We need to perform some imports at module scope in our ``models.py`` file: .. code-block:: python :linenos: @@ -160,8 +172,8 @@ file: from pyramid.security import Allow from pyramid.security import Everyone -Our root model is a ``Wiki`` object. We'll add the following line at -class scope to our ``Wiki`` class: +Our root resource object is a ``Wiki`` instance. We'll add the following +line at class scope to our ``Wiki`` class: .. code-block:: python :linenos: @@ -169,12 +181,11 @@ class scope to our ``Wiki`` class: __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] -It's only happenstance that we're assigning this ACL at class scope. -An ACL can be attached to an object *instance* too; this is how "row -level security" can be achieved in :app:`Pyramid` applications. We -actually only need *one* ACL for the entire system, however, because -our security requirements are simple, so this feature is not -demonstrated. +It's only happenstance that we're assigning this ACL at class scope. An ACL +can be attached to an object *instance* too; this is how "row level security" +can be achieved in :app:`Pyramid` applications. We actually only need *one* +ACL for the entire system, however, because our security requirements are +simple, so this feature is not demonstrated. Our resulting ``models.py`` file will now look like so: @@ -185,76 +196,71 @@ Our resulting ``models.py`` file will now look like so: Adding ``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` decorators. To do so, within -``views.py``: +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` +decorators. To do so, within ``views.py``: -- We add ``permission='view'`` to the decorator attached to the - ``view_wiki`` view function. This makes the assertion that only - users who possess the effective ``view`` permission at the time of - the request may invoke this view. We've granted - :data:`pyramid.security.Everyone` the view permission at the root - model via its ACL, so everyone will be able to invoke the - ``view_wiki`` view. +- We add ``permission='view'`` to the decorator attached to the ``view_wiki`` + view function. This makes the assertion that only users who possess the + ``view`` permission against the context resource at the time of the request + may invoke this view. We've granted :data:`pyramid.security.Everyone` the + view permission at the root model via its ACL, so everyone will be able to + invoke the ``view_wiki`` view. -- We add ``permission='view'`` to the decorator attached to the - ``view_page`` view function. This makes the assertion that only - users who possess the effective ``view`` permission at the time of +- We add ``permission='view'`` to the decorator attached to the ``view_page`` + view function. This makes the assertion that only users who possess the + effective ``view`` permission against the context resource at the time of the request may invoke this view. We've granted - :data:`pyramid.security.Everyone` the view permission at the root - model via its ACL, so everyone will be able to invoke the - ``view_page`` view. - -- We add ``permission='edit'`` to the decorator attached to the - ``add_page`` view function. This makes the assertion that only - users who possess the effective ``edit`` permission at the time of - the request may invoke this view. 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 ``add_page`` view. - We've likewise given the ``editor`` user membership to this group - via thes ``security.py`` file by mapping him to the - ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function - consults the ``GROUPS`` data structure. This means that the - ``editor`` user can add pages. - -- We add ``permission='edit'`` to the decorator attached to the - ``edit_page`` view function. This makes the assertion that only - users who possess the effective ``edit`` permission at the time of - the request may invoke this view. 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 ``edit_page`` view. - We've likewise given the ``editor`` user membership to this group - via thes ``security.py`` file by mapping him to the - ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = - {'editor':['group:editors']}``); the ``groupfinder`` function - consults the ``GROUPS`` data structure. This means that the - ``editor`` user can edit pages. + :data:`pyramid.security.Everyone` the view permission at the root model via + its ACL, so everyone will be able to invoke the ``view_page`` view. + +- We add ``permission='edit'`` to the decorator attached to the ``add_page`` + view function. This makes the assertion that only users who possess the + effective ``edit`` permission against the context resource at the time of + the request may invoke this view. 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 ``add_page`` view. We've likewise given the ``editor`` user + membership to this group via thes ``security.py`` file by mapping him to + the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = + {'editor':['group:editors']}``); the ``groupfinder`` function consults the + ``GROUPS`` data structure. This means that the ``editor`` user can add + pages. + +- We add ``permission='edit'`` to the decorator attached to the ``edit_page`` + view function. This makes the assertion that only users who possess the + effective ``edit`` permission against the context resource at the time of + the request may invoke this view. 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 ``edit_page`` view. We've likewise given the ``editor`` user + membership to this group via thes ``security.py`` file by mapping him to + the ``group:editors`` group in the ``GROUPS`` data structure (``GROUPS = + {'editor':['group:editors']}``); the ``groupfinder`` function consults the + ``GROUPS`` data structure. This means that the ``editor`` user can edit + pages. Viewing the Application in a Browser ------------------------------------ -We can finally examine our application in a browser. The views we'll -try are as follows: +We can finally examine our application in a browser. The views we'll try are +as follows: -- Visiting ``http://localhost:6543/`` in a browser invokes the - ``view_wiki`` view. This always redirects to the ``view_page`` view - of the FrontPage page object. It is executable by any user. +- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` + view. This always redirects to the ``view_page`` view of the ``FrontPage`` + page resource. It is executable by any user. -- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes - the ``view_page`` view of the front page page object. This is - because it's the :term:`default view` (a view without a ``name``) - for ``Page`` objects. It is executable by any user. +- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes the + ``view_page`` view of the ``FrontPage`` Page resource. This is because + it's the :term:`default view` (a view without a ``name``) for ``Page`` + resources. It is executable by any user. -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page 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 - credentials with the username ``editor``, password ``editor`` will - show the edit page form being displayed. +- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser invokes + the edit view for the ``FrontPage`` Page resource. 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 credentials with + the username ``editor``, password ``editor`` will show the edit page form + being displayed. - Visiting ``http://localhost:6543/add_page/SomePageName`` in a browser invokes the add view for a page. It is executable by only diff --git a/docs/tutorials/wiki/basiclayout.rst b/docs/tutorials/wiki/basiclayout.rst index c7c722f70..f6e1f800a 100644 --- a/docs/tutorials/wiki/basiclayout.rst +++ b/docs/tutorials/wiki/basiclayout.rst @@ -2,10 +2,9 @@ Basic Layout ============ -The starter files generated by the ``pyramid_zodb`` template 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. +The starter files generated by the ``pyramid_zodb`` template 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. The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/basiclayout/ @@ -21,9 +20,9 @@ well as to contain application configuration code. When you run the application using the ``paster`` command using the ``development.ini`` generated config file, the application configuration -points at an Setuptools *entry point* described as ``egg:tutorial#app``. In -our application, because the application's ``setup.py`` file says so, this -entry point happens to be the ``app`` function within the file named +points at an Setuptools *entry point* described as ``egg:tutorial``. In our +application, because the application's ``setup.py`` file says so, this entry +point happens to be the ``main`` function within the file named ``__init__.py``: .. literalinclude:: src/basiclayout/tutorial/__init__.py @@ -48,65 +47,40 @@ entry point happens to be the ``app`` function within the file named factory` and the settings keywords parsed by PasteDeploy. The root factory is named ``get_root``. -#. *Line 16*. Load the - ``configure.zcml`` file from our package using the - :meth:`pyramid.config.Configurator.load_zcml` method. - -#. *Line 17*. Use the +#. *Line 16*. Register a 'static view' which answers requests which start + with with URL path ``/static`` using the + :meth:`pyramid.config.Configurator.add_static_view method`. This + statement registers a view that will serve up static assets, such as CSS + and image files, for us, in this case, at + ``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. + +#. *Line 17*. 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``. + +#. *Line 18*. Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. -Configuration With ``configure.zcml`` --------------------------------------- - -The ``pyramid_zodb`` template uses :term:`ZCML` to perform system -configuration. The ZCML file generated by the template looks like the -following: - - .. literalinclude:: src/basiclayout/tutorial/configure.zcml - :linenos: - :language: xml - -#. *Line 1*. The root ``<configure>`` element. - -#. *Line 4*. Boilerplate, the comment explains. - -#. *Lines 6-10*. Register a ``<view>`` that names a ``context`` type - that is a class. ``.views.my_view`` is a *function* we write - (generated by the ``pyramid_zodb`` template) that is given a - ``context`` object and a ``request`` and which returns a - dictionary. The ``renderer`` tag indicates that the - ``templates/mytemplate.pt`` template should be used to turn the - dictionary returned by the view into a response. - ``templates/mytemplate.pt`` is a *relative* path: it names the - ``mytemplate.pt`` file which lives in the ``templates`` - subdirectory of the directory in which this ``configure.zcml`` - lives in. In this case, it means it lives in the ``tutorial`` - package's ``templates`` directory as ``mytemplate.pt`` - - Since this ``<view>`` doesn't have a ``name`` attribute, it is the - "default" view for that class. - -#. *Lines 12-15*. Register a ``static`` view which answers requests - which start with ``/static``. This is a view that will serve up - static resources for us, in this case, at - ``http://localhost:6543/static/`` and below. The ``path`` element - of this tag is a relative directory name, so it finds the resources - it should serve within the ``static`` directory inside - the ``tutorial`` package. - -Content Models with ``models.py`` ---------------------------------- +Resources and Models with ``models.py`` +--------------------------------------- :app:`Pyramid` uses the word :term:`resource` to describe objects arranged hierarchically in a :term:`resource tree`. This tree is consulted by :term:`traversal` to map URLs to code. In this application, the resource tree represents the site structure, but it *also* represents the -:term:`domain model` of the application, because eeach resource is a node +: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`` Paster template put the classes that implement our -resource objects, each of which happens also to be a domain model -object. +resource objects, each of which happens also to be a domain model object. Here is the source for ``models.py``: @@ -114,14 +88,13 @@ Here is the source for ``models.py``: :linenos: :language: py -#. *Lines 3-4*. The ``MyModel`` class we referred to in the ZCML file - named ``configure.zcml`` is implemented here. Instances of this - class will be capable of being persisted in :term:`ZODB` because - the class inherits from the - :class:`persistent.mapping.PersistentMapping` class. The - ``__parent__`` and ``__name__`` are important parts of the - :term:`traversal` protocol. By default, have these as ``None`` - indicating that this is the :term:`root` object. +#. *Lines 3-4*. The ``MyModel`` :term:`resource` class is implemented here. + Instances of this class will be capable of being persisted in :term:`ZODB` + because the class inherits from the + :class:`persistent.mapping.PersistentMapping` class. The ``__parent__`` + and ``__name__`` are important parts of the :term:`traversal` protocol. + By default, have these as ``None`` indicating that this is the + :term:`root` object. #. *Lines 6-12*. ``appmaker`` is used to return the *application root* object. It is called on *every request* to the @@ -134,3 +107,94 @@ Here is the source for ``models.py``: commit the transaction. We then return the application root object. +Views With ``views.py`` +----------------------- + +Our paster template generated a default ``views.py`` on our behalf. It +contains a single view, which is used to render the page shown when you visit +the URL ``http://localhost:6543/``. + +Here is the source for ``views.py``: + + .. literalinclude:: src/basiclayout/tutorial/views.py + :linenos: + :language: py + +Let's try to understand the components in this module: + +#. *Lines 1-2*. Perform some dependency imports. + +#. *Line 4*. Use the :func:`pyramid.view.view_config` :term:`configuration + 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). + + The ``@view_config`` decorator accepts a number of keyword arguments. We + use two keyword arguments here: ``context`` and ``renderer``. + + The ``context`` argument signifies that the decorated view callable should + only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` + :term:`resource` to be the :term:`context` of a request. In English, this + means that when the URL ``/`` is visited, because ``MyModel`` is the root + 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 + 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. + + Since this call to ``@view_config`` doesn't pass a ``name`` argument, the + ``my_view`` function which it decorates represents the "default" view + callable used when the context is of the type ``MyModel``. + +#. *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`` template 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. + + The function returns the dictionary ``{'project':'tutorial'}``. This + dictionary is used by the template named by the ``mytemplate.pt`` asset + specification to fill in certain values on the page. + +The WSGI Pipeline in ``development.ini`` +---------------------------------------- + +The ``development.ini`` (in the tutorial :term:`project` directory, as +opposed to the tutorial :term:`package` directory) looks like this: + +.. literalinclude:: src/views/development.ini + :linenos: + :language: ini + + +Note the existence of a ``[pipeline:main]`` section which specifies our WSGI +pipeline. This "pipeline" will be served up as our WSGI application. As far +as the WSGI server is concerned the pipeline *is* our application. Simpler +configurations don't use a pipeline: instead they expose a single WSGI +application as "main". Our setup is more complicated, so we use a pipeline +composed of :term:`middleware`. + +The ``egg:WebError#evalerror`` middleware is at the "top" of the pipeline. +This is middleware which displays debuggable errors in the browser while +you're developing (not recommended for deployment). + +The ``egg:repoze.zodbconn#closer`` middleware is in the middle of the +pipeline. This is a piece of middleware which closes the ZODB connection +opened by the ``PersistentApplicationFinder`` at the end of the request. + +The ``egg:repoze.tm#tm`` middleware is the last piece of middleware in the +pipeline. This commits a transaction near the end of the request unless +there's an exception raised. + +The final line in the ``[pipeline:main]`` section is ``tutorial``, which +refers to the ``[app:tutorial]`` section above it. The ``[app:tutorial]`` +section is the section which actually defines our application settings. The +values within this section are passed as ``**settings`` to the ``main`` +function we defined in ``__init__.py`` when the server is started via +``paster serve``. diff --git a/docs/tutorials/wiki/definingmodels.rst b/docs/tutorials/wiki/definingmodels.rst index f317d31dd..f201f6dc7 100644 --- a/docs/tutorials/wiki/definingmodels.rst +++ b/docs/tutorials/wiki/definingmodels.rst @@ -20,71 +20,67 @@ The source code for this tutorial stage can be browsed via Deleting the Database --------------------- -We're going to remove the ``MyModel`` Python model class from our -``models.py`` file. Since this class is referred to within our -persistent storage (represented on disk as a file named ``Data.fs``), +In a subsequent step, we're going to remove the ``MyModel`` Python model +class from our ``models.py`` file. Since this class is referred to within +our persistent storage (represented on disk as a file named ``Data.fs``), we'll have strange things happen the next time we want to visit the -application in a browser. Remove the ``Data.fs`` from the -``tutorial`` 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. +application in a browser. Remove the ``Data.fs`` from the ``tutorial`` +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. Adding Model Classes -------------------- The next thing we want to do is remove the ``MyModel`` class from the -generated ``models.py`` file. The ``MyModel`` class is only a sample -and we're not going to use it. +generated ``models.py`` file. The ``MyModel`` class is only a sample and +we're not going to use it. .. 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. - -Then, we'll add a ``Wiki`` class. Because this is a ZODB application, -this class should inherit from -:class:`persistent.mapping.PersistentMapping`. We want it to inherit -from the :class:`persistent.mapping.PersistentMapping` class because -our Wiki class will be a mapping of wiki page names to ``Page`` -objects. The :class:`persistent.mapping.PersistentMapping` class -provides our class with mapping behavior, and makes sure that our Wiki -page is stored as a "first-class" persistent object in our ZODB -database. - -Our ``Wiki`` class should also have a ``__name__`` attribute set to -``None`` at class scope, and should have a ``__parent__`` attribute -set to ``None`` at class scope as well. If a model has a -``__parent__`` attribute of ``None`` in a traversal-based -:app:`Pyramid` application, it means that it's the :term:`root` -model. The ``__name__`` of the root model is also always ``None``. + 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. + +Then, we'll add a ``Wiki`` class. Because this is a ZODB application, this +class should inherit from :class:`persistent.mapping.PersistentMapping`. We +want it to inherit from the :class:`persistent.mapping.PersistentMapping` +class because our Wiki class will be a mapping of wiki page names to ``Page`` +objects. The :class:`persistent.mapping.PersistentMapping` class provides +our class with mapping behavior, and makes sure that our Wiki page is stored +as a "first-class" persistent object in our ZODB database. + +Our ``Wiki`` class should also have a ``__name__`` attribute set to ``None`` +at class scope, and should have a ``__parent__`` attribute set to ``None`` at +class scope as well. If a model has a ``__parent__`` attribute of ``None`` +in a traversal-based :app:`Pyramid` application, it means that it's the +:term:`root` model. The ``__name__`` of the root model is also always +``None``. Then we'll add a ``Page`` class. This class should inherit from the -:class:`persistent.Persistent` class. We'll also give it an -``__init__`` method that accepts a single parameter named ``data``. -This parameter will contain the :term:`ReStructuredText` body -representing the wiki page content. Note that ``Page`` objects don't -have an initial ``__name__`` or ``__parent__`` attribute. All objects -in a traversal graph must have a ``__name__`` and a ``__parent__`` -attribute. We don't specify these here because both ``__name__`` and -``__parent__`` will be set by by a :term:`view` function when a Page -is added to our Wiki mapping. - -Add an Appmaker ---------------- - -We're using a mini-framework callable named -``PersistentApplicationFinder`` in our application (see ``__init__.py``). -A ``PersistentApplicationFinder`` accepts a ZODB URL as well as an -"appmaker" callback. This callback typically lives in the -``models.py`` file. - -We want to change the appmaker function in our ``models.py`` file so -that our application root is a Wiki instance, and we'll also slot a -single page object (the front page) into the wiki. +:class:`persistent.Persistent` class. We'll also give it an ``__init__`` +method that accepts a single parameter named ``data``. This parameter will +contain the :term:`ReStructuredText` body representing the wiki page content. +Note that ``Page`` objects don't have an initial ``__name__`` or +``__parent__`` attribute. All objects in a traversal graph must have a +``__name__`` and a ``__parent__`` attribute. We don't specify these here +because both ``__name__`` and ``__parent__`` will be set by by a :term:`view` +function when a Page is added to our Wiki mapping. + +As a last step, we want to change the ``appmaker`` function in our +``models.py`` file so that the :term:`root` :term:`resource` of our +application is a Wiki instance. We'll also slot a single page object (the +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. + +We're using a mini-framework callable named ``PersistentApplicationFinder`` +in our application (see ``__init__.py``). A ``PersistentApplicationFinder`` +accepts a ZODB URL as well as an "appmaker" callback. This callback +typically lives in the ``models.py`` file. We'll just change this function, +making the necessary edits. Looking at the Result of Our Edits to ``models.py`` --------------------------------------------------- @@ -96,19 +92,37 @@ something like this: :linenos: :language: python +Removing View Configuration +--------------------------- + +In a previous step in this chapter, we removed the +``tutorial.models.MyModel`` class. However, our ``views.py`` module still +attempts to import this class. Temporarily, we'll change ``views.py`` so +that it no longer references ``MyModel`` by removing its imports and removing +a reference to it from the arguments passed to the ``@view_config`` +:term:`configuration decoration` decorator which sits atop the ``my_view`` +view callable. + +The result of all of our edits to ``views.py`` will end up looking +something like this: + +.. literalinclude:: src/models/tutorial/views.py + :linenos: + :language: python + Testing the Models ------------------ -To make sure the code we just wrote works, 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 make sure the code we just wrote works, 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 classes: one for the ``Page`` model named -``PageModelTests``, one for the ``Wiki`` model named -``WikiModelTests``, and one for the appmaker named ``AppmakerTests``. +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 +classes: one for the ``Page`` model named ``PageModelTests``, one for the +``Wiki`` model named ``WikiModelTests``, and one for the appmaker named +``AppmakerTests``. When we're done changing ``tests.py``, it will look something like so: @@ -116,6 +130,22 @@ When we're done changing ``tests.py``, it will look something like so: :linenos: :language: python +Declaring Dependencies in Our ``setup.py`` File +----------------------------------------------- + +Our application now depends on packages which are not dependencies of the +original "tutorial" application as it was generated by the ``paster create`` +command. We'll add these dependencies to our ``tutorial`` package's +``setup.py`` file by assigning these dependencies to both the +``install_requires`` and the ``tests_require`` parameters to the ``setup`` +function. In particular, we require the ``docutils`` package. + +Our resulting ``setup.py`` should look like so: + +.. literalinclude:: src/models/setup.py + :linenos: + :language: python + Running the Tests ----------------- @@ -145,20 +175,3 @@ The expected output is something like this: OK -Declaring Dependencies in Our ``setup.py`` File ------------------------------------------------ - -Our application depends on packages which are not dependencies of the -original "tutorial" application as it was generated by the ``paster -create`` command. We'll add these dependencies to our ``tutorial`` -package's ``setup.py`` file by assigning these dependencies to both -the ``install_requires`` and the ``tests_require`` parameters to the -``setup`` function. In particular, we require the ``docutils`` -package. - -Our resulting ``setup.py`` should look like so: - -.. literalinclude:: src/models/setup.py - :linenos: - :language: python - diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 5250cb5e5..5b0e5dca1 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -2,28 +2,41 @@ Defining Views ============== -A :term:`view callable` in a traversal-based :app:`Pyramid` -application is typically a simple Python function that accepts two -parameters: :term:`context`, and :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 - *one* arguments: a :term:`request`. You'll see this one-argument pattern - used in other :app:`Pyramid` tutorials and applications. It was also used - in the ``my_view`` view callable that we deleted in the last chapter. - Either calling convention will work in any :app:`Pyramid` application; the - calling conventions can be used interchangeably as necessary. In - :term:`traversal` based applications, such as this tutorial, the context - is used frequently within the body of a view method, so it makes sense to - use the two-argument syntax in this application. However, in :term:`url - dispatch` based applications, the context object is rarely used in the - view body itself, so within code that uses URL-dispatch-only, it's common - to define views as callables that accept only a request to avoid the - visual "noise". - -We're going to define several :term:`view callable` functions then -wire them into :app:`Pyramid` using some :term:`view -configuration` via :term:`ZCML`. +Conventionally, :term:`view callable` objects are defined within a +``views.py`` module in an :app:`Pyramid` application. There is nothing +automagically special about the filename ``views.py``. Files implementing +views often have ``view`` in their filenames (or may live in a Python +subpackage of your application package named ``views``), but this is only by +convention. A project may have many views throughout its codebase in +arbitrarily-named files. In this application, however, we'll be continuing +to use the ``views.py`` module, because there's no reason to break +convention. + +A :term:`view callable` in a :app:`Pyramid` application is typically a simple +Python function that accepts a single parameter: :term:`request`. A view +callable is assumed to return a :term:`response` object. + +However, a :app:`Pyramid` view can also be defined as callable which accepts +*two* arguments: a :term:`context` and a :term:`request`. In :term:`url +dispatch` based applications, the context resource is rarely used in the view +body itself, so within code that uses URL-dispatch-only, it's common to +define views as callables that accept only a ``request`` to avoid the visual +"noise" of a ``context`` argument. This application, however, uses +:term:`traversal` to map URLs to a context :term:`resource`, and since our +:term:`resource tree` also represents our application's "domain model", we're +often interested in the context, because it represents the persistent storage +of our application. For this reason, having ``context`` in the callable +argument list is not "noise" to us; instead it's actually rather important +within the view code we'll define in this application. + +The single-arg (``request`` -only) or two-arg (``context`` and ``request``) +calling conventions will work in any :app:`Pyramid` application for any view; +they can be used interchangeably as necessary. We'll be using the +two-argument ``(context, request)`` view callable argument list syntax in +this application. + +We're going to define several :term:`view callable` functions then wire them +into :app:`Pyramid` using some :term:`view configuration`. The source code for this tutorial stage can be browsed via `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/views/ @@ -32,146 +45,159 @@ The source code for this tutorial stage can be browsed via Adding View Functions ===================== -We're going to add four :term:`view callable` functions to our -``views.py`` module. One view (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 named -``edit_page`` will allow a page to be edited. - -.. note:: - - There is nothing automagically special about the filename - ``views.py``. A project may have many views throughout its codebase - in arbitrarily-named files. Files implementing views often have - ``view`` in their filenames (or may live in a Python subpackage of - your application package named ``views``), but this is only by - convention. +We're going to add four :term:`view callable` functions to our ``views.py`` +module. One view 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 named ``edit_page`` will allow a page to be edited. The ``view_wiki`` view function ------------------------------- The ``view_wiki`` function will be configured to respond as the default view -of a ``Wiki`` model object. It always redirects to the ``Page`` object named -"FrontPage". It returns an instance of the +callable for a Wiki resource. We'll provide it with a ``@view_config`` +decorator which names the class ``tutorial.models.Wiki`` as its context. +This means that when a Wiki resource is the context, and no :term:`view name` +exists in the request, this view will be used. The view configuration +associated with ``view_wiki`` does not use a ``renderer`` because the view +callable always returns a :term:`response` object rather than a dictionary. +No renderer is necessary when a view returns a response object. + +The ``view_wiki`` view callable always redirects to the URL of a Page +resource named "FrontPage". To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement -the WebOb :term:`response` interface), and the -:func:`pyramid.url.resource_url` API. :func:`pyramid.url.resource_url` -constructs a URL to the ``FrontPage`` page resource -(e.g. ``http://localhost:6543/FrontPage``), and uses it as the "location" of -the HTTPFound response, forming an HTTP redirect. +the WebOb :term:`response` interface). The :func:`pyramid.url.resource_url` +API. :func:`pyramid.url.resource_url` constructs a URL to the ``FrontPage`` +page resource (e.g. ``http://localhost:6543/FrontPage``), and uses it as the +"location" of the HTTPFound response, forming an HTTP redirect. The ``view_page`` view function ------------------------------- -The ``view_page`` function will be configured to respond as the -default view of a ``Page`` object. The ``view_page`` function renders -the :term:`ReStructuredText` body of a page (stored as the ``data`` -attribute of the context passed to the view; the context will be a -Page object) as HTML. Then it substitutes an HTML anchor for each -*WikiWord* reference in the rendered HTML using a compiled regular +The ``view_page`` function will be configured to respond as the default view +of a Page resource. We'll provide it with a ``@view_config`` decorator which +names the class ``tutorial.models.Wiki`` as its context. This means that +when a Page resource is the context, and no :term:`view name` exists in the +request, this view will be used. We inform :app:`Pyramid` this view will use +the ``templates/view.pt`` template file as a ``renderer``. + +The ``view_page`` function generates the :term:`ReStructuredText` body of a +page (stored as the ``data`` attribute of the context passed to the view; the +context will be a Page resource) as HTML. Then it substitutes an HTML anchor +for each *WikiWord* reference in the rendered HTML using a compiled regular expression. The curried function named ``check`` is used as the first argument to -``wikiwords.sub``, indicating that it should be called to provide a -value for each WikiWord match found in the content. If the wiki (our -page's ``__parent__``) already contains a page with the matched -WikiWord name, the ``check`` function generates a view link to be used -as the substitution value and returns it. If the wiki does not -already contain a page with with the matched WikiWord name, the -function generates an "add" link as the substitution value and returns +``wikiwords.sub``, indicating that it should be called to provide a value for +each WikiWord match found in the content. If the wiki (our page's +``__parent__``) already contains a page with the matched WikiWord name, the +``check`` function generates a view link to be used as the substitution value +and returns it. If the wiki does not already contain a page with with the +matched WikiWord name, the function generates an "add" link as the +substitution value and returns it. + +As a result, the ``content`` variable is now a fully formed bit of HTML +containing various view and add links for WikiWords based on the content of +our current page resource. + +We then generate an edit URL (because it's easier to do here than in the +template), and we wrap up a number of arguments in a dictionary and return it. -As a result, the ``content`` variable is now a fully formed bit of -HTML containing various view and add links for WikiWords based on the -content of our current page object. - -We then generate an edit URL (because it's easier to do here than in -the template), and we wrap up a number of arguments in a dictionary -and return it. - -The arguments we wrap into a dictionary include ``page``, ``content``, -and ``edit_url``. As a result, the *template* associated with this -view callable will be able to use these names to perform various -rendering tasks. The template associated with this view callable will -be a template which lives in ``templates/view.pt``, which we'll -associate with this view via the :term:`view configuration` which -lives in the ``configure.zcml`` file. - -Note the contrast between this view callable and the ``view_wiki`` -view callable. In the ``view_wiki`` view callable, we return a -:term:`response` object. In the ``view_page`` view callable, we -return a *dictionary*. It is *always* fine to return a -:term:`response` object from a :app:`Pyramid` view. Returning a -dictionary is allowed only when there is a :term:`renderer` associated -with the view callable in the view configuration. +The arguments we wrap into a dictionary include ``page``, ``content``, and +``edit_url``. As a result, the *template* associated with this view callable +(via ``renderer=`` in its configuration) will be able to use these names to +perform various rendering tasks. The template associated with this view +callable will be a template which lives in ``templates/view.pt``. + +Note the contrast between this view callable and the ``view_wiki`` view +callable. In the ``view_wiki`` view callable, we unconditionally return a +:term:`response` object. In the ``view_page`` view callable, we return a +*dictionary*. It is *always* fine to return a :term:`response` object from a +:app:`Pyramid` view. Returning a dictionary is allowed only when there is a +:term:`renderer` associated with the view callable in the view configuration. The ``add_page`` view function ------------------------------ -The ``add_page`` function will be 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 ``context`` of the -``add_page`` view is always a Wiki object (*not* a Page object). - -The request :term:`subpath` in :app:`Pyramid` is the sequence of -names that are found *after* the view name in the URL segments given -in the ``PATH_INFO`` of the WSGI request as the result of -:term:`traversal`. If our add view is invoked via, -e.g. ``http://localhost:6543/add_page/SomeName``, the :term:`subpath` -will be a tuple: ``('SomeName',)``. - -The add view takes the zeroth element of the subpath (the wiki page -name), and aliases it to the name attribute in order to know the name -of the page we're trying to add. +The ``add_page`` function will be configured to respond when the context +resource is a Wiki and the :term:`view name` is ``add_page``. We'll provide +it with a ``@view_config`` decorator which names the string ``add_page`` as +its :term:`view name` (via name=), the class ``tutorial.models.Wiki`` as its +context, and the renderer named ``templates/edit.pt``. This means that when +a Wiki resource is the context, and a :term:`view name` named ``add_page`` +exists as the result of traversal, this view will be used. We inform +:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as +a ``renderer``. We share the same template between add and edit views, thus +``edit.pt`` instead of ``add.pt``. + +The ``add_page`` function will be 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 resource. +The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a +Page resource). + +The request :term:`subpath` in :app:`Pyramid` is the sequence of names that +are found *after* the :term:`view name` in the URL segments given in the +``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. If our +add view is invoked via, e.g. ``http://localhost:6543/add_page/SomeName``, +the :term:`subpath` will be a tuple: ``('SomeName',)``. + +The add view takes the zeroth element of the subpath (the wiki page name), +and aliases it to the name attribute in order to know the name of the page +we're trying to add. If the view rendering is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the -view renders a template. To do so, it generates a "save url" which -the template use as the form post URL during rendering. We're lazy -here, so we're trying 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``, and we'll render the template -to a response. - -If the view rendering *is* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``True``), we -scrape the page body from the form data, create a Page object using -the name in the subpath and the page body, and save it into "our -context" (the wiki) using the ``__setitem__`` method of the -context. We then redirect back to the ``view_page`` view (the default -view for a page) for the newly created page. +expression ``'form.submitted' in request.params`` is ``False``), the view +renders a template. To do so, it generates a "save url" which the template +use as the form post URL during rendering. We're lazy here, so we're trying +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 resource object in +order to satisfy the edit form's desire to have *some* page object exposed as +``page``, and we'll render the template to a response. + +If the view rendering *is* a result of a form submission (if the expression +``'form.submitted' in request.params`` is ``True``), we scrape the page body +from the form data, create a Page object using the name in the subpath and +the page body, and save it into "our context" (the Wiki) using the +``__setitem__`` method of the context. We then redirect back to the +``view_page`` view (the default view for a page) for the newly created page. The ``edit_page`` view function ------------------------------- -The ``edit_page`` function will be invoked when a user clicks the -"Edit this Page" button on the view form. It renders an edit form but -it also acts as the handler for the form it renders. The ``context`` -of the ``edit_page`` view will *always* be a Page object (never a Wiki -object). +The ``edit_page`` function will be configured to respond when the context is +a Page resource and the :term:`view name` is ``edit_page``. We'll provide it +with a ``@view_config`` decorator which names the string ``edit_page`` as its +:term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its +context, and the renderer named ``templates/edit.pt``. This means that when +a Page resource is the context, and a :term:`view name` exists as the result +of traverasal named ``edit_page``, this view will be used. We inform +:app:`Pyramid` this view will use the ``templates/edit.pt`` template file as +a ``renderer``. + +The ``edit_page`` function will be invoked when a user clicks the "Edit this +Page" button on the view form. It renders an edit form but it also acts as +the form post view callable for the form it renders. The ``context`` of the +``edit_page`` view will *always* be a Page resource (never a Wiki resource). If the view execution is *not* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``False``), the -view simply renders the edit form, passing the request, the page -object, and a save_url which will be used as the action of the -generated form. - -If the view execution *is* a result of a form submission (if the -expression ``'form.submitted' in request.params`` is ``True``), the -view grabs the ``body`` element of the request parameter and sets it -as the ``data`` attribute of the page context. It then redirects to -the default view of the context (the page), which will always be the -``view_page`` view. +expression ``'form.submitted' in request.params`` is ``False``), the view +simply renders the edit form, passing the request, the page resource, and a +save_url which will be used as the action of the generated form. + +If the view execution *is* a result of a form submission (if the expression +``'form.submitted' in request.params`` is ``True``), the view grabs the +``body`` element of the request parameter and sets it as the ``data`` +attribute of the page context. It then redirects to the default view of the +context (the page), which will always be the ``view_page`` view. Viewing the Result of Our Edits to ``views.py`` =============================================== -The result of all of our edits to ``views.py`` will leave it looking -like this: +The result of all of our edits to ``views.py`` will leave it looking like +this: .. literalinclude:: src/views/tutorial/views.py :linenos: @@ -181,20 +207,24 @@ Adding Templates ================ Most view callables we've added expected to be rendered via a -:term:`template`. Each template is a :term:`Chameleon` template. The -default templating system in :app:`Pyramid` is a variant of -:term:`ZPT` provided by Chameleon. These templates will live in the -``templates`` directory of our tutorial package. +:term:`template`. The default templating systems in :app:`Pyramid` are +:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of :term:`ZPT`, +which is an XML-based templating language. Mako is a non-XML-based +templating language. Because we had to pick one, we chose Chameleon for this +tutorial. + +The templates we create will live in the ``templates`` directory of our +tutorial package. Chameleon templates must have a ``.pt`` extension to be +recognized as such. 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 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: @@ -203,61 +233,59 @@ the below: :linenos: :language: xml -.. note:: The names available for our use in a template are always - those that are present in the dictionary returned by the view - callable. But our templates make use of a ``request`` object that - none of our tutorial views return in their dictionary. This value - appears as if "by magic". However, ``request`` is one of several - names that are available "by default" in a template when a template - renderer is used. See :ref:`chameleon_template_renderers` for more - information about other names that are available by default in a - template when a Chameleon template is used as a renderer. +.. note:: The names available for our use in a template are always those that + are present in the dictionary returned by the view callable. But our + templates make use of a ``request`` object that none of our tutorial views + return in their dictionary. This value appears as if "by magic". + However, ``request`` is one of several names that are available "by + default" in a template when a template renderer is used. See + :ref:`chameleon_template_renderers` for more information about other names + that are available by default in a template when a template is used as a + renderer. The ``edit.pt`` Template ------------------------ -The ``edit.pt`` template is used for adding and editing a wiki page. -It is used by the ``add_page`` and ``edit_page`` view functions. It -should display a page containing a form that POSTs back to the -"save_url" argument supplied by the view. The form should have a -"body" textarea field (the page data), and a submit button that has -the name "form.submitted". The textarea in the form should be filled -with any existing page data when it is rendered. +The ``edit.pt`` template is used for adding and editing a Page. It is used +by the ``add_page`` and ``edit_page`` view functions. It should display a +page containing a form that POSTs back to the "save_url" argument supplied by +the view. The form should have a "body" textarea field (the page data), and +a submit button that has the name "form.submitted". The textarea in the form +should be filled with any existing page data when it is rendered. -Once we're done with the ``edit.pt`` template, it will look a lot like -the below: +Once we're done with the ``edit.pt`` template, it will look a lot like the +below: .. literalinclude:: src/views/tutorial/templates/edit.pt :linenos: :language: xml -Static Resources ----------------- +Static Assets +------------- -Our templates name a single static resource named ``style.css``. We need to +Our templates name a single static asset named ``style.css``. We need to create this and place it in a file named ``style.css`` within our package's ``static`` directory. This file is a little too long to 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/style.css>`_. This CSS file will be accessed via -e.g. ``http://localhost:6543/static/style.css`` by virtue of the ``static`` -directive we've defined in the ``configure.zcml`` file. Any number and type -of static resources can be placed in this directory (or subdirectories) and -are just referred to by URL within templates. +e.g. ``http://localhost:6543/static/style.css`` by virtue of the call to +``add_static_view`` directive we've made in the ``__init__`` file. Any +number and type of static assets can be placed in this directory (or +subdirectories) and are just referred to by URL within templates. 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`` 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. -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 the +below: .. literalinclude:: src/views/tutorial/tests.py :linenos: @@ -266,9 +294,9 @@ the below: Running the Tests ================= -We can run these tests by using ``setup.py test`` in the same way we -did in :ref:`running_tests`. Assuming our shell's current working -directory is the "tutorial" distribution directory: +We can run these tests by using ``setup.py test`` in the same way we did in +:ref:`running_tests`. Assuming our shell's current working directory is the +"tutorial" distribution directory: On UNIX: @@ -292,123 +320,28 @@ The expected result looks something like: OK -Mapping Views to URLs in ``configure.zcml`` -=========================================== - -The ``configure.zcml`` file contains ``view`` declarations which serve -to map URLs (via :term:`traversal`) to view functions. This is also -known as :term:`view configuration`. You'll need to add four ``view`` -declarations to ``configure.zcml``. - -#. Add a declaration which maps the "Wiki" class in our ``models.py`` - file to the view named ``view_wiki`` in our ``views.py`` file with - no view name. This is the default view for a Wiki. It does not - use a ``renderer`` because the ``view_wiki`` view callable always - returns a *response* object rather than a dictionary. - -#. Add a declaration which maps the "Wiki" class in our ``models.py`` - file to the view named ``add_page`` in our ``views.py`` file with - the view name ``add_page``. Associate this view with the - ``templates/edit.pt`` template file via the ``renderer`` attribute. - This view will use the :term:`Chameleon` ZPT renderer configured - with the ``templates/edit.pt`` template to render non-*response* - return values from the ``add_page`` view. This is the add view for - a new Page. - -#. Add a declaration which maps the "Page" class in our ``models.py`` - file to the view named ``view_page`` in our ``views.py`` file with - no view name. Associate this view with the ``templates/view.pt`` - template file via the ``renderer`` attribute. This view will use - the :term:`Chameleon` ZPT renderer configured with the - ``templates/view.pt`` template to render non-*response* return - values from the ``view_page`` view. This is the default view for a - Page. - -#. Add a declaration which maps the "Page" class in our ``models.py`` - file to the view named ``edit_page`` in our ``views.py`` file with - the view name ``edit_page``. Associate this view with the - ``templates/edit.pt`` template file via the ``renderer`` attribute. - This view will use the :term:`Chameleon` ZPT renderer configured - with the ``templates/edit.pt`` template to render non-*response* - return values from the ``edit_page`` view. This is the edit view - for a page. - -As a result of our edits, the ``configure.zcml`` file should look -something like so: - -.. literalinclude:: src/views/tutorial/configure.zcml - :linenos: - :language: xml - -Examining ``development.ini`` -============================= - -Let's take a look at our ``development.ini`` file. The contents of the -file are as follows: - -.. literalinclude:: src/models/development.ini - :linenos: - :language: ini - -The WSGI Pipeline ------------------ - -Within ``development.ini``, note the existence of a ``[pipeline:main]`` -section which specifies our WSGI pipeline. This "pipeline" will be -served up as our WSGI application. As far as the WSGI server is -concerned the pipeline *is* our application. Simpler configurations -don't use a pipeline: instead they expose a single WSGI application as -"main". Our setup is more complicated, so we use a pipeline. - -``egg:repoze.zodbconn#closer`` is at the "top" of the pipeline. This -is a piece of middleware which closes the ZODB connection opened by -the PersistentApplicationFinder at the end of the request. - -``egg:repoze.tm#tm`` is the second piece of middleware in the -pipeline. This commits a transaction near the end of the request -unless there's an exception raised. - -Adding an Element to the Pipeline ---------------------------------- - -Let's add a piece of middleware to the WSGI pipeline: -``egg:Paste#evalerror`` middleware which displays debuggable errors in -the browser while you're developing (not recommended for deployment). -Let's insert evalerror into the pipeline right below -"egg:repoze.zodbconn#closer", making our resulting ``development.ini`` -file look like so: - -.. literalinclude:: src/views/development.ini - :linenos: - :language: ini - Viewing the Application in a Browser ==================================== -Once we've set up the WSGI pipeline properly, we can finally examine -our application in a browser. The views we'll try are as follows: +Once we've completed our edits, we can finally examine our application in a +browser. The views we'll try are as follows: -- Visiting ``http://localhost:6543/`` in a browser invokes the - ``view_wiki`` view. This always redirects to the ``view_page`` view - of the FrontPage page object. +- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` + view. This always redirects to the ``view_page`` view of the ``FrontPage`` + Page resource. - Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes - the ``view_page`` view of the front page page object. This is + the ``view_page`` view of the front page resource. This is because it's the *default view* (a view without a ``name``) for Page - objects. + resources. - Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page object. + invokes the edit view for the ``FrontPage`` Page resource. - Visiting ``http://localhost:6543/add_page/SomePageName`` in a - browser invokes the add view for a page. + browser invokes the add view for a Page. - To generate an error, visit ``http://localhost:6543/add_page`` which will generate an ``IndexError`` for the expression ``request.subpath[0]``. You'll see an interactive traceback - facility provided by evalerror. - - - - - + facility provided by :term:`WebError`. diff --git a/docs/tutorials/wiki/index.rst b/docs/tutorials/wiki/index.rst index 68f724902..589935047 100644 --- a/docs/tutorials/wiki/index.rst +++ b/docs/tutorials/wiki/index.rst @@ -22,7 +22,6 @@ tutorial can be browsed at basiclayout definingmodels definingviews - viewdecorators authorization distributing diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index cb05611b7..82265170d 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -251,7 +251,8 @@ assumptions: - you are willing to use :term:`traversal` to map URLs to code. -- you want to use :term:`ZCML` to perform configuration. +- you want to use imperative code plus a :term:`scan` to perform + configuration. .. note:: diff --git a/docs/tutorials/wiki/src/authorization/development.ini b/docs/tutorials/wiki/src/authorization/development.ini index a102b721b..6f4c33d93 100644 --- a/docs/tutorials/wiki/src/authorization/development.ini +++ b/docs/tutorials/wiki/src/authorization/development.ini @@ -4,12 +4,14 @@ reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false +debug_templates = true +default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = + egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:Paste#evalerror egg:repoze.tm#tm tutorial @@ -17,3 +19,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py index 742627a3f..3e9266754 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/__init__.py @@ -1,6 +1,11 @@ -from pyramid.config import Configurator from repoze.zodbconn.finder import PersistentApplicationFinder + +from pyramid.config import Configurator +from pyramid.authentication import AuthTktAuthenticationPolicy +from pyramid.authorization import ACLAuthorizationPolicy + from tutorial.models import appmaker +from tutorial.security import groupfinder def main(global_config, **settings): """ This function returns a WSGI application. @@ -8,6 +13,9 @@ def main(global_config, **settings): It is usually called by the PasteDeploy framework during ``paster serve``. """ + authn_policy = AuthTktAuthenticationPolicy(secret='sosecret', + callback=groupfinder) + authz_policy = ACLAuthorizationPolicy() zodb_uri = settings.get('zodb_uri') if zodb_uri is None: raise ValueError("No 'zodb_uri' in application configuration.") @@ -15,6 +23,9 @@ def main(global_config, **settings): finder = PersistentApplicationFinder(zodb_uri, appmaker) def get_root(request): return finder(request.environ) - config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') + config = Configurator(root_factory=get_root, settings=settings, + authentication_policy=authn_policy, + authorization_policy=authz_policy) + config.add_static_view('static', 'tutorial:static') + config.scan('tutorial') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/authorization/tutorial/configure.zcml b/docs/tutorials/wiki/src/authorization/tutorial/configure.zcml deleted file mode 100644 index d0e65516e..000000000 --- a/docs/tutorials/wiki/src/authorization/tutorial/configure.zcml +++ /dev/null @@ -1,25 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <scan package="."/> - - <view - view=".login.login" - renderer="templates/login.pt" - context="pyramid.exceptions.Forbidden"/> - - <authtktauthenticationpolicy - secret="sosecret" - callback=".security.groupfinder" - /> - - <aclauthorizationpolicy/> - - <static - name="static" - path="static" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/authorization/tutorial/login.py b/docs/tutorials/wiki/src/authorization/tutorial/login.py index a1194feb0..463db71a6 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/login.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/login.py @@ -1,15 +1,16 @@ from pyramid.httpexceptions import HTTPFound -from pyramid.view import view_config -from pyramid.url import resource_url - from pyramid.security import remember from pyramid.security import forget +from pyramid.view import view_config +from pyramid.url import resource_url -from tutorial.models import Wiki from tutorial.security import USERS -@view_config(context=Wiki, name='login', renderer='templates/login.pt') +@view_config(context='tutorial.models.Wiki', name='login', + renderer='templates/login.pt') +@view_config(context='pyramid.exceptions.Forbidden', + renderer='templates/login.pt') def login(request): login_url = resource_url(request.context, request, 'login') referrer = request.url @@ -36,7 +37,7 @@ def login(request): password = password, ) -@view_config(context=Wiki, name='logout') +@view_config(context='tutorial.models.Wiki', name='logout') def logout(request): headers = forget(request) return HTTPFound(location = resource_url(request.context, request), diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py index 3143ab552..183cb2a8d 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py @@ -3,22 +3,20 @@ import re from pyramid.httpexceptions import HTTPFound from pyramid.url import resource_url - -from pyramid.security import authenticated_userid - from pyramid.view import view_config +from pyramid.security import authenticated_userid from tutorial.models import Page -from tutorial.models import Wiki # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context=Wiki, permission='view') +@view_config(context='tutorial.models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location = resource_url(context, request, 'FrontPage')) -@view_config(context=Page, renderer='templates/view.pt', permission='view') +@view_config(context='tutorial.models.Page', + renderer='templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ @@ -41,7 +39,8 @@ def view_page(context, request): return dict(page = context, content = content, edit_url = edit_url, logged_in = logged_in) -@view_config(context=Wiki, name='add_page', renderer='templates/edit.pt', +@view_config(name='add_page', context='tutorial.models.Wiki', + renderer='templates/edit.pt', permission='edit') def add_page(context, request): name = request.subpath[0] @@ -61,7 +60,8 @@ def add_page(context, request): return dict(page = page, save_url = save_url, logged_in = logged_in) -@view_config(context=Page, name='edit_page', renderer='templates/edit.pt', +@view_config(name='edit_page', context='tutorial.models.Page', + renderer='templates/edit.pt', permission='edit') def edit_page(context, request): if 'form.submitted' in request.params: diff --git a/docs/tutorials/wiki/src/basiclayout/CHANGES.txt b/docs/tutorials/wiki/src/basiclayout/CHANGES.txt index ffa255da8..35a34f332 100644 --- a/docs/tutorials/wiki/src/basiclayout/CHANGES.txt +++ b/docs/tutorials/wiki/src/basiclayout/CHANGES.txt @@ -1,4 +1,4 @@ 0.0 --- -- Initial version +- Initial version diff --git a/docs/tutorials/wiki/src/basiclayout/development.ini b/docs/tutorials/wiki/src/basiclayout/development.ini index fdae922e9..6f4c33d93 100644 --- a/docs/tutorials/wiki/src/basiclayout/development.ini +++ b/docs/tutorials/wiki/src/basiclayout/development.ini @@ -4,10 +4,13 @@ reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false +debug_templates = true +default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = + egg:WebError#evalerror egg:repoze.zodbconn#closer egg:repoze.tm#tm tutorial @@ -16,3 +19,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/basiclayout/setup.cfg b/docs/tutorials/wiki/src/basiclayout/setup.cfg index 3d7ea6e23..23b2ad983 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.cfg +++ b/docs/tutorials/wiki/src/basiclayout/setup.cfg @@ -25,4 +25,3 @@ domain = tutorial input_file = tutorial/locale/tutorial.pot output_dir = tutorial/locale previous = true - diff --git a/docs/tutorials/wiki/src/basiclayout/setup.py b/docs/tutorials/wiki/src/basiclayout/setup.py index a0de6ec81..7fb15b782 100644 --- a/docs/tutorials/wiki/src/basiclayout/setup.py +++ b/docs/tutorials/wiki/src/basiclayout/setup.py @@ -19,9 +19,8 @@ setup(name='tutorial', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ - "Intended Audience :: Developers", - "Framework :: Pylons", "Programming Language :: Python", + "Framework :: Pylons", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], @@ -32,8 +31,8 @@ setup(name='tutorial', packages=find_packages(), include_package_data=True, zip_safe=False, - install_requires=requires, - tests_require=requires, + install_requires = requires, + tests_require= requires, test_suite="tutorial", entry_points = """\ [paste.app_factory] @@ -41,3 +40,4 @@ setup(name='tutorial', """, paster_plugins=['pyramid'], ) + diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py index 8325865c0..a9f776980 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py @@ -13,6 +13,6 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') + config.add_static_view('static', 'tutorial:static') + config.scan('tutorial') return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/configure.zcml b/docs/tutorials/wiki/src/basiclayout/tutorial/configure.zcml deleted file mode 100644 index ab7fd6fbe..000000000 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/configure.zcml +++ /dev/null @@ -1,17 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <view - context=".models.MyModel" - view=".views.my_view" - renderer="templates/mytemplate.pt" - /> - - <static - name="static" - path="static" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt index a5a0dd214..6ad23d44f 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt @@ -8,57 +8,67 @@ <link rel="shortcut icon" href="${request.application_url}/static/favicon.ico" /> <link rel="stylesheet" href="${request.application_url}/static/pylons.css" type="text/css" media="screen" charset="utf-8" /> <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Nobile:regular,italic,bold,bolditalic&subset=latin" type="text/css" media="screen" charset="utf-8" /> + <!--[if !IE 7]> + <style type="text/css"> + #wrap {display:table;height:100%} + </style> + <![endif]--> </head> <body> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> + <div id="wrap"> + <div id="header"> + <div class="header">The Pyramid Web Application Development Framework</div> </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h3>Search Pyramid documentation</h3> - <form method="get" action="http://docs.pylonshq.com/pyramid/dev/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Search" /> - </form> + <div id="top"> + <div class="top align-center"> + <img src="${request.application_url}/static/logo.png" width="300" height="80"/> + <p class="app-welcome"> + Welcome to <span class="app-name">${project}</span>, an application generated by<br/> + the Pyramid web application development framework. + </p> </div> - <div id="right" class="align-left"> - <h3>Pyramid links</h3> - <ul class="links"> - <li> - <a href="http://pylonshq.com">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#api-documentation">API Documentation</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#tutorials">Tutorials</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#change-history">Change History</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#sample-applications">Sample Applications</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#support-and-development">Support and Development</a> - </li> - <li> - <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> - </li> - </ul> + </div> + <div id="bottom"> + <div class="bottom"> + <div id="left" class="align-right"> + <h3>Search Pyramid documentation</h3> + <form method="get" action="http://docs.pylonshq.com/pyramid/dev/search.html"> + <input type="text" id="q" name="q" value="" /> + <input type="submit" id="x" value="Search" /> + </form> + </div> + <div id="right" class="align-left"> + <h3>Pyramid links</h3> + <ul class="links"> + <li> + <a href="http://pylonshq.com">Pylons Website</a> + </li> + <li> + <a href="http://docs.pylonshq.com/">The Pylons Project Documentation</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#narrative-documentation">Narrative Documentation</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#api-documentation">API Documentation</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#tutorials">Tutorials</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#change-history">Change History</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#sample-applications">Sample Applications</a> + </li> + <li> + <a href="http://docs.pylonshq.com/pyramid/dev/#support-and-development">Support and Development</a> + </li> + <li> + <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> + </li> + </ul> + </div> </div> </div> </div> diff --git a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py index 26ee6671e..0a3d507a0 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py @@ -16,3 +16,4 @@ class ViewTests(unittest.TestCase): 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 93d619d83..555f49e6d 100644 --- a/docs/tutorials/wiki/src/basiclayout/tutorial/views.py +++ b/docs/tutorials/wiki/src/basiclayout/tutorial/views.py @@ -1,2 +1,6 @@ +from pyramid.view import view_config +from tutorial.models import MyModel + +@view_config(context=MyModel, renderer='tutorial:templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/models/development.ini b/docs/tutorials/wiki/src/models/development.ini index fdae922e9..6f4c33d93 100644 --- a/docs/tutorials/wiki/src/models/development.ini +++ b/docs/tutorials/wiki/src/models/development.ini @@ -4,10 +4,13 @@ reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false +debug_templates = true +default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = + egg:WebError#evalerror egg:repoze.zodbconn#closer egg:repoze.tm#tm tutorial @@ -16,3 +19,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/models/tutorial/__init__.py b/docs/tutorials/wiki/src/models/tutorial/__init__.py index cf0d14b2d..bf0f683bf 100644 --- a/docs/tutorials/wiki/src/models/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/models/tutorial/__init__.py @@ -4,9 +4,6 @@ from tutorial.models import appmaker def main(global_config, **settings): """ This function returns a WSGI application. - - It is usually called by the PasteDeploy framework during - ``paster serve``. """ zodb_uri = settings.get('zodb_uri') if zodb_uri is None: @@ -16,6 +13,7 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') + config.add_static_view('static', 'tutorial:static') + config.scan('tutorial') return config.make_wsgi_app() diff --git a/docs/tutorials/wiki/src/models/tutorial/configure.zcml b/docs/tutorials/wiki/src/models/tutorial/configure.zcml deleted file mode 100644 index 38675eb13..000000000 --- a/docs/tutorials/wiki/src/models/tutorial/configure.zcml +++ /dev/null @@ -1,17 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <view - context=".models.Wiki" - view=".views.my_view" - renderer="templates/mytemplate.pt" - /> - - <static - name="static" - path="static" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/models/tutorial/views.py b/docs/tutorials/wiki/src/models/tutorial/views.py index 93d619d83..2346602c9 100644 --- a/docs/tutorials/wiki/src/models/tutorial/views.py +++ b/docs/tutorials/wiki/src/models/tutorial/views.py @@ -1,2 +1,5 @@ +from pyramid.view import view_config + +@view_config(renderer='tutorial:templates/mytemplate.pt') def my_view(request): return {'project':'tutorial'} diff --git a/docs/tutorials/wiki/src/viewdecorators/CHANGES.txt b/docs/tutorials/wiki/src/viewdecorators/CHANGES.txt deleted file mode 100644 index ffa255da8..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/CHANGES.txt +++ /dev/null @@ -1,4 +0,0 @@ -0.0 ---- - -- Initial version diff --git a/docs/tutorials/wiki/src/viewdecorators/README.txt b/docs/tutorials/wiki/src/viewdecorators/README.txt deleted file mode 100644 index d41f7f90f..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/README.txt +++ /dev/null @@ -1,4 +0,0 @@ -tutorial README - - - diff --git a/docs/tutorials/wiki/src/viewdecorators/development.ini b/docs/tutorials/wiki/src/viewdecorators/development.ini deleted file mode 100644 index a102b721b..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/development.ini +++ /dev/null @@ -1,19 +0,0 @@ -[app:tutorial] -use = egg:tutorial -reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false -zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 - -[pipeline:main] -pipeline = - egg:repoze.zodbconn#closer - egg:Paste#evalerror - egg:repoze.tm#tm - tutorial - -[server:main] -use = egg:Paste#http -host = 0.0.0.0 -port = 6543 diff --git a/docs/tutorials/wiki/src/viewdecorators/setup.cfg b/docs/tutorials/wiki/src/viewdecorators/setup.cfg deleted file mode 100644 index 3d7ea6e23..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/setup.cfg +++ /dev/null @@ -1,28 +0,0 @@ -[nosetests] -match=^test -nocapture=1 -cover-package=tutorial -with-coverage=1 -cover-erase=1 - -[compile_catalog] -directory = tutorial/locale -domain = tutorial -statistics = true - -[extract_messages] -add_comments = TRANSLATORS: -output_file = tutorial/locale/tutorial.pot -width = 80 - -[init_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale - -[update_catalog] -domain = tutorial -input_file = tutorial/locale/tutorial.pot -output_dir = tutorial/locale -previous = true - diff --git a/docs/tutorials/wiki/src/viewdecorators/setup.py b/docs/tutorials/wiki/src/viewdecorators/setup.py deleted file mode 100644 index 5ee1333bc..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -import os - -from setuptools import setup, find_packages - -here = os.path.abspath(os.path.dirname(__file__)) -README = open(os.path.join(here, 'README.txt')).read() -CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() - -requires = [ - 'pyramid', - 'repoze.zodbconn', - 'repoze.tm', - 'ZODB3', - 'WebError', - 'docutils', - ] - -setup(name='tutorial', - version='0.0', - description='tutorial', - long_description=README + '\n\n' + CHANGES, - classifiers=[ - "Intended Audience :: Developers", - "Framework :: Pylons", - "Programming Language :: Python", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", - ], - author='', - author_email='', - url='', - keywords='web pylons pyramid', - packages=find_packages(), - include_package_data=True, - zip_safe=False, - install_requires=requires, - tests_require=requires, - test_suite="tutorial", - entry_points = """\ - [paste.app_factory] - main = tutorial:main - """, - paster_plugins = ['pyramid'], - ) diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py deleted file mode 100644 index cf0d14b2d..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from pyramid.config import Configurator -from repoze.zodbconn.finder import PersistentApplicationFinder -from tutorial.models import appmaker - -def main(global_config, **settings): - """ This function returns a WSGI application. - - It is usually called by the PasteDeploy framework during - ``paster serve``. - """ - zodb_uri = settings.get('zodb_uri') - if zodb_uri is None: - raise ValueError("No 'zodb_uri' in application configuration.") - - finder = PersistentApplicationFinder(zodb_uri, appmaker) - def get_root(request): - return finder(request.environ) - config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') - return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/configure.zcml b/docs/tutorials/wiki/src/viewdecorators/tutorial/configure.zcml deleted file mode 100644 index be5b84c43..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/configure.zcml +++ /dev/null @@ -1,13 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <scan package="."/> - - <static - name="static" - path="static" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/models.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/models.py deleted file mode 100644 index 9761856c6..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/models.py +++ /dev/null @@ -1,22 +0,0 @@ -from persistent import Persistent -from persistent.mapping import PersistentMapping - -class Wiki(PersistentMapping): - __name__ = None - __parent__ = None - -class Page(Persistent): - def __init__(self, data): - self.data = data - -def appmaker(zodb_root): - if not 'app_root' in zodb_root: - app_root = Wiki() - frontpage = Page('This is the front page') - app_root['FrontPage'] = frontpage - frontpage.__name__ = 'FrontPage' - frontpage.__parent__ = app_root - zodb_root['app_root'] = app_root - import transaction - transaction.commit() - return zodb_root['app_root'] diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/favicon.ico b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/favicon.ico Binary files differdeleted file mode 100644 index 71f837c9e..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/favicon.ico +++ /dev/null diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png Binary files differdeleted file mode 100644 index 88f5d9865..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/logo.png +++ /dev/null diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/pylons.css b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/pylons.css deleted file mode 100644 index c153be07f..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/pylons.css +++ /dev/null @@ -1,73 +0,0 @@ -html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */ -vertical-align:baseline;background:transparent;} -body{line-height:1;} -ol,ul{list-style:none;} -blockquote,q{quotes:none;} -blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} -/* remember to define focus styles! */ -:focus{outline:0;} -/* remember to highlight inserts somehow! */ -ins{text-decoration:none;} -del{text-decoration:line-through;} -/* tables still need 'cellspacing="0"' in the markup */ -table{border-collapse:collapse;border-spacing:0;} -/* restyling */ -sub{vertical-align:sub;font-size:smaller;line-height:normal;} -sup{vertical-align:super;font-size:smaller;line-height:normal;} -/* lists */ -ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;} -ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;} -li{display:list-item;} -/* nested lists have no top/bottom margins */ -ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;} -/* 2 deep unordered lists use a circle */ -ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;} -/* 3 deep (or more) unordered lists use a square */ -ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;} -.hidden{display:none;} -p{line-height:1.5em;} -h1{font-size:1.75em;/* 28px */ -line-height:1.7em;font-family:helvetica,verdana;} -h2{font-size:1.5em;/* 24px */ -line-height:1.7em;font-family:helvetica,verdana;} -h3{font-size:1.25em;/* 20px */ -line-height:1.7em;font-family:helvetica,verdana;} -h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} -html,body{width:100%;height:100%;} -body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} -a{color:#1b61d6;text-decoration:none;} -a:hover{color:#e88f00;text-decoration:underline;} -body h1, -body h2, -body h3, -body h4, -body h5, -body h6{font-family:"Nobile","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#144fb2;font-style:normal;} -#wrap {min-height: 100%;} -#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;} -#header{background-color:#e88f00;top:0;font-size:14px;} -#footer{background-color:#000000;bottom:0;position: relative;margin-top:-40px;clear:both;} -.header,.footer{width:700px;margin-right:auto;margin-left:auto;} -.wrapper{width:100%} -#top,#bottom{width:100%;} -#top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} -#bottom{color:#222;background-color:#ffffff;overflow:hidden;padding-bottom:80px;} -.top,.bottom{width:700px;margin-right:auto;margin-left:auto;} -.top{padding-top:100px;} -.app-welcome{margin-top:25px;} -.app-name{color:#000000;font-weight:bold;} -.bottom{padding-top:50px;} -#left{width:325px;float:left;padding-right:25px;} -#right{width:325px;float:right;padding-left:25px;} -.align-left{text-align:left;} -.align-right{text-align:right;} -.align-center{text-align:center;} -ul.links{margin:0;padding:0;} -ul.links li{list-style-type:none;font-size:14px;} -form{border-style:none;} -fieldset{border-style:none;} -input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;} -input[type=text]{} -input[type=submit]{background-color:#ddd;font-weight:bold;} -/*Opera Fix*/ -body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/style.css b/docs/tutorials/wiki/src/viewdecorators/tutorial/static/style.css deleted file mode 100644 index cad87e0d4..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/static/style.css +++ /dev/null @@ -1,109 +0,0 @@ -html, body { - color: black; - background-color: #ddd; - font: x-small "Lucida Grande", "Lucida Sans Unicode", geneva, sans-serif; - margin: 0; - padding: 0; -} - -td, th {padding:3px;border:none;} -tr th {text-align:left;background-color:#f0f0f0;color:#333;} -tr.odd td {background-color:#edf3fe;} -tr.even td {background-color:#fff;} - -#header { - height: 80px; - width: 777px; - background: blue URL('../images/header_inner.png') no-repeat; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - margin: 0 auto 0 auto; -} - -a.link, a, a.active { - color: #369; -} - - -#main_content { - color: black; - font-size: 127%; - background-color: white; - width: 757px; - margin: 0 auto 0 auto; - border-left: 1px solid #aaa; - border-right: 1px solid #aaa; - padding: 10px; -} - -#sidebar { - border: 1px solid #aaa; - background-color: #eee; - margin: 0.5em; - padding: 1em; - float: right; - width: 200px; - font-size: 88%; -} - -#sidebar h2 { - margin-top: 0; -} - -#sidebar ul { - margin-left: 1.5em; - padding-left: 0; -} - -h1,h2,h3,h4,h5,h6,#getting_started_steps { - font-family: "Century Schoolbook L", Georgia, serif; - font-weight: bold; -} - -h2 { - font-size: 150%; -} - -#footer { - border: 1px solid #aaa; - border-top: 0px none; - color: #999; - background-color: white; - padding: 10px; - font-size: 80%; - text-align: center; - width: 757px; - margin: 0 auto 1em auto; -} - -.code { - font-family: monospace; -} - -span.code { - font-weight: bold; - background: #eee; -} - -#status_block { - margin: 0 auto 0.5em auto; - padding: 15px 10px 15px 55px; - background: #cec URL('../images/ok.png') left center no-repeat; - border: 1px solid #9c9; - width: 450px; - font-size: 120%; - font-weight: bolder; -} - -.notice { - margin: 0.5em auto 0.5em auto; - padding: 15px 10px 15px 55px; - width: 450px; - background: #eef URL('../images/info.png') left center no-repeat; - border: 1px solid #cce; -} - -.fielderror { - color: red; - font-weight: bold; -} diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/edit.pt b/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/edit.pt deleted file mode 100644 index 525bd43df..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/edit.pt +++ /dev/null @@ -1,30 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" - xmlns:tal="http://xml.zope.org/namespaces/tal"> - -<head> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki) Editing: ${page.__name__}</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> -</head> - -<body> - -<div class="main_content"> - <div style="float:right; width: 10em;"> Viewing - <span tal:replace="page.__name__">Page Name Goes Here</span> <br/> - You can return to the <a href="${request.application_url}">FrontPage</a>. - </div> - - <div> - <form action="${save_url}" method="post"> - <textarea name="body" tal:content="page.data" rows="10" cols="60"/> - <input type="submit" name="form.submitted" value="Save"/> - </form> - </div> -</div> -</body> -</html> diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/mytemplate.pt b/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/mytemplate.pt deleted file mode 100644 index a5a0dd214..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/mytemplate.pt +++ /dev/null @@ -1,69 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"> -<head> - <title>The Pyramid Web Application Development Framework</title> - <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.application_url}/static/favicon.ico" /> - <link rel="stylesheet" href="${request.application_url}/static/pylons.css" type="text/css" media="screen" charset="utf-8" /> - <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Nobile:regular,italic,bold,bolditalic&subset=latin" type="text/css" media="screen" charset="utf-8" /> -</head> -<body> - <div id="header"> - <div class="header">The Pyramid Web Application Development Framework</div> - </div> - <div id="top"> - <div class="top align-center"> - <img src="${request.application_url}/static/logo.png" width="300" height="80"/> - <p class="app-welcome"> - Welcome to <span class="app-name">${project}</span>, an application generated by<br/> - the Pyramid web application development framework. - </p> - </div> - </div> - <div id="bottom"> - <div class="bottom"> - <div id="left" class="align-right"> - <h3>Search Pyramid documentation</h3> - <form method="get" action="http://docs.pylonshq.com/pyramid/dev/search.html"> - <input type="text" id="q" name="q" value="" /> - <input type="submit" id="x" value="Search" /> - </form> - </div> - <div id="right" class="align-left"> - <h3>Pyramid links</h3> - <ul class="links"> - <li> - <a href="http://pylonshq.com">Pylons Website</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#narrative-documentation">Narrative Documentation</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#api-documentation">API Documentation</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#tutorials">Tutorials</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#change-history">Change History</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#sample-applications">Sample Applications</a> - </li> - <li> - <a href="http://docs.pylonshq.com/pyramid/dev/#support-and-development">Support and Development</a> - </li> - <li> - <a href="irc://irc.freenode.net#pyramid">IRC Channel</a> - </li> - </ul> - </div> - </div> - </div> - <div id="footer"> - <div class="footer">© Copyright 2008-2010, Agendaless Consulting.</div> - </div> -</body> -</html>
\ No newline at end of file diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/view.pt b/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/view.pt deleted file mode 100644 index 0fc68aa2f..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/templates/view.pt +++ /dev/null @@ -1,26 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html - xmlns="http://www.w3.org/1999/xhtml" - xmlns:tal="http://xml.zope.org/namespaces/tal"> - -<head> - <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> - <title>${page.__name__} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title> - <link rel="stylesheet" type="text/css" - href="${request.application_url}/static/style.css" /> -</head> - -<body> - -<div class="main_content"> -<div style="float:right; width: 10em;"> Viewing -<span tal:replace="page.__name__">Page Name Goes Here</span> <br/> -You can return to the <a href="${request.application_url}">FrontPage</a>. -</div> - -<div tal:replace="structure content">Page text goes here.</div> -<p><a tal:attributes="href edit_url" href="">Edit this page</a></p> -</div> - -</body></html> diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/tests.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/tests.py deleted file mode 100644 index aaf753816..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/tests.py +++ /dev/null @@ -1,124 +0,0 @@ -import unittest - -from pyramid import testing - -class PageModelTests(unittest.TestCase): - - def _getTargetClass(self): - from tutorial.models import Page - return Page - - def _makeOne(self, data=u'some data'): - return self._getTargetClass()(data=data) - - 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 - return Wiki - - def _makeOne(self): - return self._getTargetClass()() - - def test_it(self): - wiki = self._makeOne() - self.assertEqual(wiki.__parent__, None) - self.assertEqual(wiki.__name__, None) - -class AppmakerTests(unittest.TestCase): - def _callFUT(self, zodb_root): - from tutorial.models import appmaker - return appmaker(zodb_root) - - def test_it(self): - root = {} - self._callFUT(root) - self.assertEqual(root['app_root']['FrontPage'].data, - 'This is the front page') - -class ViewWikiTests(unittest.TestCase): - def test_it(self): - from tutorial.views import view_wiki - context = testing.DummyResource() - request = testing.DummyRequest() - response = view_wiki(context, request) - self.assertEqual(response.location, 'http://example.com/FrontPage') - -class ViewPageTests(unittest.TestCase): - def _callFUT(self, context, request): - from tutorial.views import view_page - return view_page(context, request) - - def test_it(self): - wiki = testing.DummyResource() - wiki['IDoExist'] = testing.DummyResource() - context = testing.DummyResource(data='Hello CruelWorld IDoExist') - context.__parent__ = wiki - context.__name__ = 'thepage' - request = testing.DummyRequest() - info = self._callFUT(context, request) - self.assertEqual(info['page'], context) - self.assertEqual( - info['content'], - '<div class="document">\n' - '<p>Hello <a href="http://example.com/add_page/CruelWorld">' - 'CruelWorld</a> ' - '<a href="http://example.com/IDoExist/">' - 'IDoExist</a>' - '</p>\n</div>\n') - self.assertEqual(info['edit_url'], - 'http://example.com/thepage/edit_page') - - -class AddPageTests(unittest.TestCase): - def _callFUT(self, context, request): - from tutorial.views import add_page - return add_page(context, request) - - def test_it_notsubmitted(self): - from pyramid.url import resource_url - context = testing.DummyResource() - request = testing.DummyRequest() - request.subpath = ['AnotherPage'] - info = self._callFUT(context, request) - self.assertEqual(info['page'].data,'') - self.assertEqual(info['save_url'], - resource_url( - context, request, 'add_page', 'AnotherPage')) - - def test_it_submitted(self): - context = testing.DummyResource() - request = testing.DummyRequest({'form.submitted':True, - 'body':'Hello yo!'}) - request.subpath = ['AnotherPage'] - self._callFUT(context, request) - page = context['AnotherPage'] - self.assertEqual(page.data, 'Hello yo!') - self.assertEqual(page.__name__, 'AnotherPage') - self.assertEqual(page.__parent__, context) - -class EditPageTests(unittest.TestCase): - def _callFUT(self, context, request): - from tutorial.views import edit_page - return edit_page(context, request) - - def test_it_notsubmitted(self): - from pyramid.url import resource_url - context = testing.DummyResource() - request = testing.DummyRequest() - info = self._callFUT(context, request) - self.assertEqual(info['page'], context) - self.assertEqual(info['save_url'], - resource_url(context, request, 'edit_page')) - - def test_it_submitted(self): - context = testing.DummyResource() - request = testing.DummyRequest({'form.submitted':True, - 'body':'Hello yo!'}) - response = self._callFUT(context, request) - self.assertEqual(response.location, 'http://example.com/') - self.assertEqual(context.data, 'Hello yo!') diff --git a/docs/tutorials/wiki/src/viewdecorators/tutorial/views.py b/docs/tutorials/wiki/src/viewdecorators/tutorial/views.py deleted file mode 100644 index c8ac46edf..000000000 --- a/docs/tutorials/wiki/src/viewdecorators/tutorial/views.py +++ /dev/null @@ -1,62 +0,0 @@ -from docutils.core import publish_parts -import re - -from pyramid.httpexceptions import HTTPFound -from pyramid.url import resource_url -from pyramid.view import view_config - -from tutorial.models import Page -from tutorial.models import Wiki - -# regular expression used to find WikiWords -wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") - -@view_config(context=Wiki) -def view_wiki(context, request): - return HTTPFound(location = resource_url(context, request, 'FrontPage')) - -@view_config(context=Page, renderer='templates/view.pt') -def view_page(context, request): - wiki = context.__parent__ - - def check(match): - word = match.group(1) - if word in wiki: - page = wiki[word] - view_url = resource_url(page, request) - return '<a href="%s">%s</a>' % (view_url, word) - else: - add_url = request.application_url + '/add_page/' + word - return '<a href="%s">%s</a>' % (add_url, word) - - content = publish_parts(context.data, writer_name='html')['html_body'] - content = wikiwords.sub(check, content) - edit_url = resource_url(context, request, 'edit_page') - return dict(page = context, content = content, edit_url = edit_url) - -@view_config(context=Wiki, name='add_page', renderer='templates/edit.pt') -def add_page(context, request): - name = request.subpath[0] - if 'form.submitted' in request.params: - body = request.params['body'] - page = Page(body) - page.__name__ = name - page.__parent__ = context - context[name] = page - return HTTPFound(location = resource_url(page, request)) - save_url = resource_url(context, request, 'add_page', name) - page = Page('') - page.__name__ = name - page.__parent__ = context - return dict(page = page, save_url = save_url) - -@view_config(context=Page, name='edit_page', renderer='templates/edit.pt') -def edit_page(context, request): - if 'form.submitted' in request.params: - context.data = request.params['body'] - return HTTPFound(location = resource_url(context, request)) - - return dict(page = context, - save_url = resource_url(context, request, 'edit_page')) - - diff --git a/docs/tutorials/wiki/src/views/development.ini b/docs/tutorials/wiki/src/views/development.ini index a102b721b..6f4c33d93 100644 --- a/docs/tutorials/wiki/src/views/development.ini +++ b/docs/tutorials/wiki/src/views/development.ini @@ -4,12 +4,14 @@ reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false +debug_templates = true +default_locale_name = en zodb_uri = file://%(here)s/Data.fs?connection_cache_size=20000 [pipeline:main] pipeline = + egg:WebError#evalerror egg:repoze.zodbconn#closer - egg:Paste#evalerror egg:repoze.tm#tm tutorial @@ -17,3 +19,29 @@ pipeline = use = egg:Paste#http host = 0.0.0.0 port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/docs/tutorials/wiki/src/views/tutorial/__init__.py b/docs/tutorials/wiki/src/views/tutorial/__init__.py index cf0d14b2d..91f7c2624 100644 --- a/docs/tutorials/wiki/src/views/tutorial/__init__.py +++ b/docs/tutorials/wiki/src/views/tutorial/__init__.py @@ -4,9 +4,6 @@ from tutorial.models import appmaker def main(global_config, **settings): """ This function returns a WSGI application. - - It is usually called by the PasteDeploy framework during - ``paster serve``. """ zodb_uri = settings.get('zodb_uri') if zodb_uri is None: @@ -16,6 +13,6 @@ def main(global_config, **settings): def get_root(request): return finder(request.environ) config = Configurator(root_factory=get_root, settings=settings) - config.load_zcml('configure.zcml') + config.add_static_view('static', 'tutorial:static') + config.scan('tutorial') return config.make_wsgi_app() - diff --git a/docs/tutorials/wiki/src/views/tutorial/configure.zcml b/docs/tutorials/wiki/src/views/tutorial/configure.zcml deleted file mode 100644 index c1b1d6ce8..000000000 --- a/docs/tutorials/wiki/src/views/tutorial/configure.zcml +++ /dev/null @@ -1,36 +0,0 @@ -<configure xmlns="http://pylonshq.com/pyramid"> - - <!-- this must be included for the view declarations to work --> - <include package="pyramid.includes" /> - - <static - name="static" - path="static" - /> - - <view - context=".models.Wiki" - view=".views.view_wiki" - /> - - <view - context=".models.Wiki" - name="add_page" - view=".views.add_page" - renderer="templates/edit.pt" - /> - - <view - context=".models.Page" - view=".views.view_page" - renderer="templates/view.pt" - /> - - <view - context=".models.Page" - name="edit_page" - view=".views.edit_page" - renderer="templates/edit.pt" - /> - -</configure> diff --git a/docs/tutorials/wiki/src/views/tutorial/views.py b/docs/tutorials/wiki/src/views/tutorial/views.py index 8437fdc51..c96bc2e9c 100644 --- a/docs/tutorials/wiki/src/views/tutorial/views.py +++ b/docs/tutorials/wiki/src/views/tutorial/views.py @@ -3,15 +3,19 @@ import re from pyramid.httpexceptions import HTTPFound from pyramid.url import resource_url +from pyramid.view import view_config from tutorial.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') def view_wiki(context, request): return HTTPFound(location = resource_url(context, request, 'FrontPage')) +@view_config(context='tutorial.models.Page', + renderer='tutorial:templates/view.pt') def view_page(context, request): wiki = context.__parent__ @@ -29,7 +33,9 @@ def view_page(context, request): content = wikiwords.sub(check, content) edit_url = resource_url(context, request, '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') def add_page(context, request): name = request.subpath[0] if 'form.submitted' in request.params: @@ -44,7 +50,9 @@ def add_page(context, request): page.__name__ = name 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') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] diff --git a/docs/tutorials/wiki/viewdecorators.rst b/docs/tutorials/wiki/viewdecorators.rst deleted file mode 100644 index c2f068d86..000000000 --- a/docs/tutorials/wiki/viewdecorators.rst +++ /dev/null @@ -1,240 +0,0 @@ -========================================================== -Using View Decorators Rather than ZCML ``view`` directives -========================================================== - -So far we've been using :term:`ZCML` to map model types to views. -It's often easier to use the :class:`pyramid.view.view_config` view -decorator to do this mapping. Using view decorators provides better -locality of reference for the mapping, because you can see which model -types and view names the view will serve right next to the view -function itself. In this mode, however, you lose the ability for some -views to be overridden "from the outside" (by someone using your -application as a framework, as explained in the -:ref:`extending_chapter`). Since this application is not meant to be -a framework, it makes sense for us to switch over to using view -decorators. - -Adding View Decorators -====================== - -We're going to import the :class:`pyramid.view.view_config` callable. -This callable can be used as a function, class, or method decorator. -We'll use it to decorate our ``view_wiki``, ``view_page``, -``add_page`` and ``edit_page`` view functions. - -The :class:`pyramid.view.view_config` callable accepts a number of -arguments: - -``context`` - - The model type which the :term:`context` of our view will be, in our - case a class. - -``name`` - - The name of the view. - -``renderer`` - - The renderer (usually a *template name*) that will be used when the - view returns a non-:term:`response` object. - -There are other arguments which this callable accepts, but these are -the ones we're going to use. - -The ``view_wiki`` view function -------------------------------- - -The decorator above the ``view_wiki`` function will be: - -.. ignore-next-block -.. code-block:: python - :linenos: - - @view_config(context=Wiki) - -This indicates that the view is for the Wiki class and has the *empty* -view_name (indicating the :term:`default view` for the Wiki class). -After injecting this decorator, we can now *remove* the following from -our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - context=".models.Wiki" - view=".views.view_wiki" - /> - -Our new decorator takes its place. - -The ``view_page`` view function -------------------------------- - -The decorator above the ``view_page`` function will be: - -.. ignore-next-block -.. code-block:: python - :linenos: - - @view_config(context=Page, renderer='templates/view.pt') - -This indicates that the view is for the Page class and has the *empty* -view_name (indicating the :term:`default view` for the Page class). -After injecting this decorator, we can now *remove* the following from -our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - context=".models.Page" - view=".views.view_page" - renderer="templates/view.pt" - /> - -Our new decorator takes its place. - -The ``add_page`` view function ------------------------------- - -The decorator above the ``add_page`` function will be: - -.. ignore-next-block -.. code-block:: python - :linenos: - - @view_config(context=Wiki, name='add_page', renderer='templates/edit.pt') - -This indicates that the view is for the Wiki class and has the -``add_page`` view_name. After injecting this decorator, we can now -*remove* the following from our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - context=".models.Wiki" - name="add_page" - view=".views.add_page" - renderer="templates/edit.pt" - /> - -Our new decorator takes its place. - -The ``edit_page`` view function -------------------------------- - -The decorator above the ``edit_page`` function will be: - -.. ignore-next-block -.. code-block:: python - :linenos: - - @view_config(context=Page, name='edit_page', renderer='templates/edit.pt') - -This indicates that the view is for the Page class and has the -``edit_page`` view_name. After injecting this decorator, we can now -*remove* the following from our ``configure.zcml`` file: - -.. code-block:: xml - :linenos: - - <view - context=".models.Page" - name="edit_page" - view=".views.edit_page" - renderer="templates/edit.pt" - /> - -Our new decorator takes its place. - -Adding a Scan Directive -======================= - -In order for our decorators to be recognized, we must add a bit of -boilerplate to our ``configure.zcml`` file which tells -:app:`Pyramid` to kick off a :term:`scan` at startup time. Add the -following tag anywhere beneath the ``<include -package="pyramid.includes">`` tag but before the ending -``</configure>`` tag within ``configure.zcml``: - -.. code-block:: xml - :linenos: - - <scan package="."/> - -Viewing the Result of Our Edits to ``views.py`` -=============================================== - -The result of all of our edits to ``views.py`` will leave it looking -like this: - -.. literalinclude:: src/viewdecorators/tutorial/views.py - :linenos: - :language: python - -Viewing the Results of Our Edits to ``configure.zcml`` -====================================================== - -The result of all of our edits to ``configure.zcml`` will leave it -looking like this: - -.. literalinclude:: src/viewdecorators/tutorial/configure.zcml - :linenos: - :language: xml - -Running the Tests -================= - -We can run these tests by using ``setup.py test`` in the same way we -did in :ref:`running_tests`. Assuming our shell's current working -directory is the "tutorial" distribution directory: - -On UNIX: - -.. code-block:: text - - $ ../bin/python setup.py test -q - -On Windows: - -.. code-block:: text - - c:\bigfntut\tutorial> ..\Scripts\python setup.py test -q - -Hopefully nothing will have changed. The expected result looks -something like: - -.. code-block:: text - - ......... - ---------------------------------------------------------------------- - Ran 9 tests in 0.203s - - OK - -Viewing the Application in a Browser -==================================== - -Once we've set up the WSGI pipeline properly, we can finally examine -our application in a browser. We'll make sure that we didn't break -any views by trying each of them. - -- Visiting ``http://localhost:6543/`` in a - browser invokes the ``view_wiki`` view. This always redirects to - the ``view_page`` view of the FrontPage page object. - -- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes - the ``view_page`` view of the front page page object. This is - because it's the *default view* (a view without a ``name``) for Page - objects. - -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page object. - -- Visiting ``http://localhost:6543/add_page/SomePageName`` in a - browser invokes the add view for a page. - - - |
