summaryrefslogtreecommitdiff
path: root/docs/tutorials/wiki2/definingviews.rst
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2010-10-25 17:58:11 -0400
committerChris McDonough <chrism@plope.com>2010-10-25 17:58:11 -0400
commite26700528995b1807c5e55a4295a6f788a5603de (patch)
tree439e71c28d716bf5d5b742cee2578e6247da4dc3 /docs/tutorials/wiki2/definingviews.rst
parent4b679b4d7cf4a045293f9e652aee818cd8649dc3 (diff)
downloadpyramid-e26700528995b1807c5e55a4295a6f788a5603de.tar.gz
pyramid-e26700528995b1807c5e55a4295a6f788a5603de.tar.bz2
pyramid-e26700528995b1807c5e55a4295a6f788a5603de.zip
adjust wiki2 tutorial for pyramid
Diffstat (limited to 'docs/tutorials/wiki2/definingviews.rst')
-rw-r--r--docs/tutorials/wiki2/definingviews.rst404
1 files changed, 404 insertions, 0 deletions
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst
new file mode 100644
index 000000000..1bba7efea
--- /dev/null
+++ b/docs/tutorials/wiki2/definingviews.rst
@@ -0,0 +1,404 @@
+==============
+Defining Views
+==============
+
+A :term:`view callable` in a :term:`url dispatch` -based
+:mod:`pyramid` application is typically a simple Python function that
+accepts a single parameter named :term:`request`. A view callable is
+assumed to return a :term:`response` object.
+
+.. note:: A :mod:`pyramid` view can also be defined as callable
+ which accepts *two* arguments: a :term:`context` and a
+ :term:`request`. You'll see this two-argument pattern used in
+ other :mod:`pyramid` tutorials and applications. Either calling
+ convention will work in any :mod:`pyramid` application; the
+ calling conventions can be used interchangeably as necessary. In
+ :term:`url dispatch` based applications, however, the context
+ object is rarely used in the view body itself, so within this
+ tutorial we define views as callables that accept only a request to
+ avoid the visual "noise". If you do need the ``context`` within a
+ view function that only takes the request as a single argument, you
+ can obtain it via ``request.context``.
+
+The request passed to every view that is called as the result of a
+route match has an attribute named ``matchdict`` that contains the
+elements placed into the URL by the ``pattern`` of a ``route``
+statement. For instance, if a route statement in ``configure.zcml``
+had the pattern ``:one/:two``, and the URL at
+``http://example.com/foo/bar`` was invoked, matching this pattern, the
+matchdict dictionary attached to the request passed to the view would
+have a ``one`` key with the value ``foo`` and a ``two`` key with the
+value ``bar``.
+
+The source code for this tutorial stage can be browsed at
+`http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/
+<http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/>`_.
+
+Declaring Dependencies in Our ``setup.py`` File
+===============================================
+
+The view code in our application will depend on a package which is not
+a dependency of the original "tutorial" application. The original
+"tutorial" application was generated by the ``paster create`` command;
+it doesn't know about our custom application requirements. We need to
+add a dependency on the ``docutils`` package to our ``tutorial``
+package's ``setup.py`` file by assigning this dependency to the
+``install_requires`` parameter in the ``setup`` function.
+
+Our resulting ``setup.py`` should look like so:
+
+.. literalinclude:: src/views/setup.py
+ :linenos:
+ :language: python
+
+.. note:: After these new dependencies are added, you will need to
+ rerun ``python setup.py develop`` inside the root of the
+ ``tutorial`` package to obtain and register the newly added
+ dependency package.
+
+Adding View Functions
+=====================
+
+We'll get rid of our ``my_view`` view function in our ``views.py``
+file. It's only an example and isn't relevant to our application.
+
+Then we're going to add four :term:`view callable` functions to our
+``views.py`` module. One view callable (named ``view_wiki``) will
+display the wiki itself (it will answer on the root URL), another
+named ``view_page`` will display an individual page, another named
+``add_page`` will allow a page to be added, and a final view callable
+named ``edit_page`` will allow a page to be edited. We'll describe
+each one briefly and show the resulting ``views.py`` file afterward.
+
+.. note::
+
+ There is nothing special about the filename ``views.py``. A project
+ may have many view callables throughout its codebase in
+ arbitrarily-named files. Files implementing view callables 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.
+
+The ``view_wiki`` view function
+-------------------------------
+
+The ``view_wiki`` function will respond as the :term:`default view` of
+a ``Wiki`` model object. It always redirects to a URL which
+represents the path to our "FrontPage". It returns an instance of the
+:class:`webob.exc.HTTPFound` class (instances of which implement the
+WebOb :term:`response` interface), It will use the
+:func:`pyramid.url.route_url` API to construct a URL to the
+``FrontPage`` page (e.g. ``http://localhost:6543/FrontPage``), and
+will use it as the "location" of the HTTPFound response, forming an
+HTTP redirect.
+
+The ``view_page`` view function
+-------------------------------
+
+The ``view_page`` function will respond as the :term:`default view` of
+a ``Page`` object. The ``view_page`` function renders the
+:term:`ReStructuredText` body of a page (stored as the ``data``
+attribute of a Page object) as HTML. Then it substitutes an HTML
+anchor for each *WikiWord* reference in the rendered HTML using a
+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
+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 object.
+
+We then generate an edit URL (because it's easier to do here than in
+the template), and we return a dictionary with a number of arguments.
+The fact that this view returns a dictionary (as opposed to a
+:term:`response` object) is a cue to :mod:`pyramid` that it should
+try to use a :term:`renderer` associated with the view configuration
+to render a template. In our case, the template which will be
+rendered will be the ``templates/view.pt`` template, as per the
+configuration put into effect in ``configure.zcml``.
+
+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 ``matchdict`` attribute of the
+request passed to the ``add_page`` view will have the values we need
+to construct URLs and find model objects.
+
+The matchdict will have a ``pagename`` key that matches the name of
+the page we'd like to add. If our add view is invoked via,
+e.g. ``http://localhost:6543/add_page/SomeName``, the ``pagename``
+value in the matchdict will be ``SomeName``.
+
+If the view execution is *not* a result of a form submission (if the
+expression ``'form.submitted' in request.params`` is ``False``), the
+view callable renders a template. To do so, it generates a "save url"
+which the template use as the form post URL during rendering. We're
+lazy here, so we're trying to use the same template
+(``templates/edit.pt``) for the add view as well as the page edit
+view, so we create a dummy Page object in order to satisfy the edit
+form's desire to have *some* page object exposed as ``page``, and
+:mod:`pyramid` will render the template associated with this view
+to a response.
+
+If the view execution *is* a result of a form submission (if the
+expression ``'form.submitted' in request.params`` is ``True``), we
+scrape the page body from the form data, create a Page object using
+the name in the matchdict ``pagename``, and obtain the page body from
+the request, and save it into the database using ``session.add``. We
+then redirect back to the ``view_page`` view (the :term:`default view`
+for a Page) for the newly created page.
+
+The ``edit_page`` view function
+-------------------------------
+
+The ``edit_page`` function will be invoked when a user clicks the
+"Edit this Page" button on the view form. It renders an edit form but
+it also acts as the handler for the form it renders. The
+``matchdict`` attribute of the request passed to the ``add_page`` view
+will have a ``pagename`` key matching the name of the page the user
+wants to edit.
+
+If the view execution is *not* a result of a form submission (if the
+expression ``'form.submitted' in request.params`` is ``False``), the
+view simply renders the edit form, passing the request, the page
+object, and a save_url which will be used as the action of the
+generated form.
+
+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`` key in the matchdict. It then redirects to the
+default view of the wiki 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:
+
+.. literalinclude:: src/views/tutorial/views.py
+ :linenos:
+ :language: python
+
+Adding Templates
+================
+
+The views we've added all reference a :term:`template`. Each template
+is a :term:`Chameleon` template. The default templating system in
+:mod:`pyramid` is a variant of :term:`ZPT` provided by
+:term:`Chameleon`. These templates will live in the ``templates``
+directory of our tutorial package.
+
+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).
+
+Once we're done with the ``view.pt`` template, it will look a lot like
+the below:
+
+.. literalinclude:: src/views/tutorial/templates/view.pt
+ :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.
+
+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.
+
+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
+----------------
+
+Our templates name a single static resource named ``style.css``. We
+need to create this and place it in a file named ``style.css`` within
+our package's ``templates/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/wiki2/src/views/tutorial/templates/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.
+
+Mapping Views to URLs in ``configure.zcml``
+===========================================
+
+The ``configure.zcml`` file contains ``route`` declarations (and a
+lone ``view`` declaration) which serve to map URLs via :term:`url
+dispatch` to view functions. First, we’ll get rid of the existing
+``route`` created by the template using the name ``home``. It’s only
+an example and isn’t relevant to our application.
+
+We then need to add four ``route`` declarations to ``configure.zcml``.
+Note that the *ordering* of these declarations is very important.
+``route`` declarations are matched in the order they're found in the
+``configure.zcml`` file.
+
+#. Add a declaration which maps the empty pattern (signifying the root
+ URL) to the view named ``view_wiki`` in our ``views.py`` file with
+ the name ``view_wiki``. This is the :term:`default view` for the
+ wiki.
+
+#. Add a declaration which maps the pattern ``:pagename`` to the
+ view named ``view_page`` in our ``views.py`` file with the view
+ name ``view_page``. This is the regular view for a page.
+
+#. Add a declaration which maps the pattern
+ ``:pagename/edit_page`` to the view named ``edit_page`` in our
+ ``views.py`` file with the name ``edit_page``. This is the edit view
+ for a page.
+
+#. Add a declaration which maps the pattern
+ ``add_page/:pagename`` to the view named ``add_page`` in our
+ ``views.py`` file with the name ``add_page``. This is the add view
+ for a new 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
+
+The WSGI Pipeline
+-----------------
+
+Within ``tutorial.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.tm2#tm`` is at the "top" of the pipeline. This is a
+piece of middleware which commits a transaction if no exception
+occurs; if an exception occurs, the transaction will be aborted. This
+is the piece of software that allows us to forget about needing to do
+manual commits and aborts of our database connection in view code.
+
+Adding an Element to the Pipeline
+---------------------------------
+
+Let's add a piece of middleware to the WSGI pipeline. We'll add
+``egg:Paste#evalerror`` middleware which displays debuggable errors in
+the browser while you're developing (this is *not* recommended for
+deployment as it is a security risk). Let's insert evalerror into the
+pipeline right above ``egg:repoze.tm2#tm``, making our resulting
+``tutorial.ini`` file look like so:
+
+.. literalinclude:: src/views/tutorial.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:
+
+- 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.
+
+- 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.
+
+Try generating an error within the body of a view by adding code to
+the top of it that generates an exception (e.g. ``raise
+Exception('Forced Exception')``). Then visit the error-raising view
+in a browser. You should see an interactive exception handler in the
+browser which allows you to examine values in a post-mortem mode.
+
+Adding Tests
+============
+
+Since we've added a good bit of imperative code here, it's useful to
+define tests for the views we've created. We'll change our tests.py
+module to look like this:
+
+.. literalinclude:: src/views/tutorial/tests.py
+ :linenos:
+ :language: python
+
+We can then run the tests using something like:
+
+.. code-block:: text
+ :linenos:
+
+ $ python setup.py test -q
+
+The expected output is something like:
+
+.. code-block:: text
+ :linenos:
+
+ running test
+ running egg_info
+ writing requirements to tutorial.egg-info/requires.txt
+ writing tutorial.egg-info/PKG-INFO
+ writing top-level names to tutorial.egg-info/top_level.txt
+ writing dependency_links to tutorial.egg-info/dependency_links.txt
+ writing entry points to tutorial.egg-info/entry_points.txt
+ unrecognized .svn/entries format in
+ reading manifest file 'tutorial.egg-info/SOURCES.txt'
+ writing manifest file 'tutorial.egg-info/SOURCES.txt'
+ running build_ext
+ ......
+ ----------------------------------------------------------------------
+ Ran 6 tests in 0.181s
+
+ OK
+
+
+
+