diff options
Diffstat (limited to 'docs/tutorials/wiki2/definingviews.rst')
| -rw-r--r-- | docs/tutorials/wiki2/definingviews.rst | 616 |
1 files changed, 376 insertions, 240 deletions
diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index cea376b77..996bff88c 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -1,343 +1,479 @@ +.. _wiki2_defining_views: + ============== Defining Views ============== -A :term:`view callable` in a :term:`url dispatch` -based :app:`Pyramid` -application is typically a simple Python function that accepts a single -parameter named :term:`request`. A view callable is assumed to return a -:term:`response` object. - -.. note:: A :app:`Pyramid` view can also be defined as callable - which accepts *two* arguments: a :term:`context` and a - :term:`request`. You'll see this two-argument pattern used in - other :app:`Pyramid` tutorials and applications. Either calling - convention will work in any :app:`Pyramid` application; the - calling conventions can be used interchangeably as necessary. In - :term:`url dispatch` based applications, however, the context - object is rarely used in the view body itself, so within this - tutorial we define views as callables that accept only a request to - avoid the visual "noise". If you do need the ``context`` within a - view function that only takes the request as a single argument, you - can obtain it via ``request.context``. - -The request passed to every view that is called as the result of a route -match has an attribute named ``matchdict`` that contains the elements placed -into the URL by the ``pattern`` of a ``route`` statement. For instance, if a -call to :meth:`pyramid.config.Configurator.add_route` in ``__init__.py`` had -the pattern ``{one}/{two}``, and the URL at ``http://example.com/foo/bar`` -was invoked, matching this pattern, the ``matchdict`` dictionary attached to -the request passed to the view would have a ``'one'`` key with the value -``'foo'`` and a ``'two'`` key with the value ``'bar'``. - -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: +A :term:`view callable` in a :app:`Pyramid` application is typically a simple +Python function that accepts a single parameter named :term:`request`. A view +callable is assumed to return a :term:`response` object. + +The request object has a dictionary as an attribute named ``matchdict``. A +``matchdict`` maps the placeholders in the matching URL ``pattern`` to the +substrings of the path in the :term:`request` URL. For instance, if a call to +:meth:`pyramid.config.Configurator.add_route` has the pattern ``/{one}/{two}``, +and a user visits ``http://example.com/foo/bar``, our pattern would be matched +against ``/foo/bar`` and the ``matchdict`` would look like ``{'one':'foo', +'two':'bar'}``. + + +Adding the ``docutils`` dependency +================================== + +Remember in the previous chapter we added a new dependency of the ``bcrypt`` +package. Again, the view code in our application will depend on a package which +is not a dependency of the original "tutorial" application. + +We need to add a dependency on the ``docutils`` package to our ``tutorial`` +package's ``setup.py`` file by assigning this dependency to the ``requires`` +parameter in the ``setup()`` function. + +Open ``tutorial/setup.py`` and edit it to look like the following: .. literalinclude:: src/views/setup.py :linenos: + :emphasize-lines: 13 :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. +Only the highlighted line needs to be added. + +Again, as we did in the previous chapter, the dependency now needs to be +installed, so re-run the ``$VENV/bin/pip install -e .`` command. + + +Static assets +------------- + +Our templates name static assets, including CSS and images. We don't need +to create these files within our package's ``static`` directory because they +were provided at the time we created the project. + +As an example, the CSS file will be accessed via +``http://localhost:6543/static/theme.css`` by virtue of the call to the +``add_static_view`` directive we've made in the ``routes.py`` file. Any number +and type of static assets can be placed in this directory (or subdirectories) +and are just referred to by URL or by using the convenience method +``static_url``, e.g., ``request.static_url('<package>:static/foo.css')`` within +templates. + + +Adding routes to ``routes.py`` +============================== + +This is the `URL Dispatch` tutorial, so let's start by adding some URL patterns +to our app. Later we'll attach views to handle the URLs. + +The ``routes.py`` file contains :meth:`pyramid.config.Configurator.add_route` +calls which serve to add routes to our application. First we'll get rid of the +existing route created by the template using the name ``'home'``. It's only an +example and isn't relevant to our application. -Adding View Functions -===================== +We then need to add four calls to ``add_route``. Note that the *ordering* of +these declarations is very important. Route declarations are matched in the +order they're registered. -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. +#. Add a declaration which maps the pattern ``/`` (signifying the root URL) to + the route named ``view_wiki``. In the next step, we will map it to our + ``view_wiki`` view callable by virtue of the ``@view_config`` decorator + attached to the ``view_wiki`` view function, which in turn will be indicated + by ``route_name='view_wiki'``. -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. +#. Add a declaration which maps the pattern ``/{pagename}`` to the route named + ``view_page``. This is the regular view for a page. Again, in the next step, + we will map it to our ``view_page`` view callable by virtue of the + ``@view_config`` decorator attached to the ``view_page`` view function, + whin in turn will be indicated by ``route_name='view_page'``. + +#. Add a declaration which maps the pattern ``/add_page/{pagename}`` to the + route named ``add_page``. This is the add view for a new page. We will map + it to our ``add_page`` view callable by virtue of the ``@view_config`` + decorator attached to the ``add_page`` view function, which in turn will be + indicated by ``route_name='add_page'``. + +#. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the + route named ``edit_page``. This is the edit view for a page. We will map it + to our ``edit_page`` view callable by virtue of the ``@view_config`` + decorator attached to the ``edit_page`` view function, which in turn will be + indicated by ``route_name='edit_page'``. + +As a result of our edits, the ``routes.py`` file should look like the +following: + +.. literalinclude:: src/views/tutorial/routes.py + :linenos: + :emphasize-lines: 3-6 + :language: python + +The highlighted lines are the ones that need to be added or edited. + +.. warning:: + + The order of the routes is important! If you placed + ``/{pagename}/edit_page`` *before* ``/add_page/{pagename}``, then we would + never be able to add pages. This is because the first route would always + match a request to ``/add_page/edit_page`` whereas we want ``/add_page/..`` + to have priority. This isn't a huge problem in this particular app because + wiki pages are always camel case, but it's important to be aware of this + behavior in your own apps. + + +Adding view functions in ``views/default.py`` +============================================= + +It's time for a major change. Open ``tutorial/views/default.py`` and +edit it to look like the following: + +.. literalinclude:: src/views/tutorial/views/default.py + :linenos: + :language: python + :emphasize-lines: 1-9,12- + +The highlighted lines need to be added or edited. + +We added some imports, and created a regular expression to find "WikiWords". + +We got rid of the ``my_view`` view function and its decorator that was added +when we originally rendered the ``alchemy`` scaffold. It was only an example +and isn't relevant to our application. We also deleted the ``db_err_msg`` +string. + +Then we added four :term:`view callable` functions to our ``views/default.py`` +module, as mentioned in the previous step: + +* ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL. +* ``view_page()`` - Displays an individual page. +* ``edit_page()`` - Allows the user to edit a page. +* ``add_page()`` - Allows the user to add a page. + +We'll describe each one briefly in the following sections. .. 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. + There is nothing special about the filename ``default.py`` exept that it is a + Python module. A project may have many view callables throughout its codebase + in arbitrarily named modules. Modules implementing view callables often have + ``view`` in their name (or may live in a Python subpackage of your + application package named ``views``, as in our case), but this is only by + convention, not a requirement. + The ``view_wiki`` view function ------------------------------- -The ``view_wiki`` function is the :term:`default view` that will be called -when a request is made to the root URL of our wiki. It always redirects to -a URL which represents the path to our "FrontPage". +Following is the code for the ``view_wiki`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py - :pyobject: view_wiki +.. literalinclude:: src/views/tutorial/views/default.py + :lines: 17-20 + :lineno-match: :linenos: :language: python -The ``view_wiki`` function returns an instance of the +``view_wiki()`` is the :term:`default view` that gets called when a request is +made to the root URL of our wiki. It always redirects to a URL which +represents the path to our "FrontPage". + +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), 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 :class:`pyramid.interfaces.IResponse` interface, like +:class:`pyramid.response.Response`). It uses the +:meth:`pyramid.request.Request.route_url` API to construct a URL to the +``FrontPage`` page (i.e., ``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 used to show a single page of our -wiki. It 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. +Here is the code for the ``view_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py - :pyobject: view_page +.. literalinclude:: src/views/tutorial/views/default.py + :lines: 22-42 + :lineno-match: :linenos: :language: python -The curried function named ``check`` is used as the first argument to +``view_page()`` is used to display a single page of our wiki. It renders the +:term:`reStructuredText` body of a page (stored as the ``data`` attribute of a +``Page`` model 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 ``add_link`` 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 +page with the matched WikiWord name, ``add_link()`` 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 +not already contain a page with the matched WikiWord name, ``add_link()`` 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) +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 +``view_page()`` returns a dictionary (as opposed to a :term:`response` object) is a cue to :app:`Pyramid` that it should try to use a :term:`renderer` -associated with the view configuration to render a template. In our case, -the template which will be rendered will be the ``templates/view.pt`` -template, as per the configuration put into effect in ``__init__.py``. - -The ``add_page`` view function ------------------------------- +associated with the view configuration to render a response. In our case, the +renderer used will be the ``view.jinja2`` template, as indicated in +the ``@view_config`` decorator that is applied to ``view_page()``. -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. +If the page does not exist, then we need to handle that by raising a +:class:`pyramid.httpexceptions.HTTPNotFound` to trigger our 404 handling, +defined in ``tutorial/views/notfound.py``. -.. literalinclude:: src/views/tutorial/views.py - :pyobject: add_page - :linenos: - :language: python +.. note:: -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 value for -``'pagename'`` in the ``matchdict`` will be ``'SomeName'``. + Using ``raise`` versus ``return`` with the HTTP exceptions is an important + distinction that can commonly mess people up. In + ``tutorial/views/notfound.py`` there is an :term:`exception view` + registered for handling the ``HTTPNotFound`` exception. Exception views are + only triggered for raised exceptions. If the ``HTTPNotFound`` is returned, + then it has an internal "stock" template that it will use to render itself + as a response. If you aren't seeing your exception view being executed, this + is most likely the problem! See :ref:`special_exceptions_in_callables` for + more information about exception views. -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 uses as the form post URL during rendering. We're lazy here, so -we're trying to use the same template (``templates/edit.pt``) for the add -view as well as the page edit view, so we create a dummy Page object in order -to satisfy the edit form's desire to have *some* page object exposed as -``page``, and :app:`Pyramid` will render the template associated with this -view to a response. - -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 with this page body and the name -taken from ``matchdict['pagename']``, and save it into the database using -``session.add``. We then redirect back to the ``view_page`` view 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 ``edit_page`` view will have a ``'pagename'`` key -matching the name of the page the user wants to edit. +Here is the code for the ``edit_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py - :pyobject: edit_page +.. literalinclude:: src/views/tutorial/views/default.py + :lines: 44-56 + :lineno-match: :linenos: :language: python -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 page object and a ``save_url`` -which will be used as the action of the generated form. +``edit_page()`` is 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 which it renders. The ``matchdict`` attribute of the request passed to the +``edit_page`` view will have a ``'pagename'`` key matching the name of the page +that the user wants to edit. -If the view execution *is* a result of a form submission (if the expression +If the view execution *is* a result of a form submission (i.e., the expression ``'form.submitted' in request.params`` is ``True``), the view grabs the ``body`` element of the request parameters and sets it as the ``data`` attribute of the page object. It then redirects to the ``view_page`` view of the wiki page. -Viewing the Result of all Our Edits to ``views.py`` -=================================================== +If the view execution is *not* a result of a form submission (i.e., the +expression ``'form.submitted' in request.params`` is ``False``), the view +simply renders the edit form, passing the page object and a ``save_url`` +which will be used as the action of the generated form. + +.. note:: + + Since our ``request.dbsession`` defined in the previous chapter is + registered with the ``pyramid_tm`` transaction manager, any changes we make + to objects managed by the that session will be committed automatically. In + the event that there was an error (even later, in our template code), the + changes would be aborted. This means the view itself does not need to + concern itself with commit/rollback logic. + + +The ``add_page`` view function +------------------------------ -The result of all of our edits to ``views.py`` will leave it looking -like this: +Here is the code for the ``add_page`` view function and its decorator: -.. literalinclude:: src/views/tutorial/views.py +.. literalinclude:: src/views/tutorial/views/default.py + :lines: 58- + :lineno-match: :linenos: :language: python -Adding Templates -================ +``add_page()`` is invoked when a user clicks on a *WikiWord* which isn't yet +represented as a page in the system. The ``add_link`` function within the +``view_page`` view generates URLs to this view. ``add_page()`` also acts as a +handler for the form that is generated when we want to add a page object. The +``matchdict`` attribute of the request passed to the ``add_page()`` view will +have the values we need to construct URLs and find model objects. + +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, for example, +``http://localhost:6543/add_page/SomeName``, the value for ``'pagename'`` in +the ``matchdict`` will be ``'SomeName'``. + +Next a check is performed to determine whether the ``Page`` already exists in +the database. If it already exists, then the client is redirected to the +``edit_page`` view, else we continue to the next check. + +If the view execution *is* a result of a form submission (i.e., the expression +``'form.submitted' in request.params`` is ``True``), we grab the page body from +the form data, create a Page object with this page body and the name taken from +``matchdict['pagename']``, and save it into the database using +``request.dbession.add``. Since we have not yet covered authentication, we +don't have a logged-in user to add as the page's ``creator``. Until we get to +that point in the tutorial, we'll just assume that all pages are created by the +``editor`` user. Thus we query for that object, and set it on ``page.creator``. +Finally, we redirect the client back to the ``view_page`` view for the newly +created page. + +If the view execution is *not* a result of a form submission (i.e., the +expression ``'form.submitted' in request.params`` is ``False``), the view +callable renders a template. To do so, it generates a ``save_url`` which the +template uses as the form post URL during rendering. We're lazy here, so +we're going to use the same template (``templates/edit.jinja2``) for the add +view as well as the page edit view. To do so we create a dummy ``Page`` object +in order to satisfy the edit form's desire to have *some* page object +exposed as ``page``. :app:`Pyramid` will render the template associated +with this view to a response. -The views we've added all reference a :term:`template`. Each template is a -:term:`Chameleon` :term:`ZPT` template. These templates will live in the -``templates`` directory of our tutorial package. -The ``view.pt`` Template ------------------------- +Adding templates +================ -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_page``, ``add_page`` and ``edit_page`` views that we've added +reference a :term:`template`. Each template is a :term:`Jinja2` template. +These templates will live in the ``templates`` directory of our tutorial +package. Jinja2 templates must have a ``.jinja2`` extension to be recognized +as such. -Once we're done with the ``view.pt`` template, it will look a lot like the -below: -.. literalinclude:: src/views/tutorial/templates/view.pt - :language: xml +The ``layout.jinja2`` template +------------------------------ -.. 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. +Update ``tutorial/templates/layout.jinja2`` with the following content, as +indicated by the emphasized lines: -The ``edit.pt`` Template ------------------------- +.. literalinclude:: src/views/tutorial/templates/layout.jinja2 + :linenos: + :emphasize-lines: 11,35-36 + :language: html -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. +Since we're using a templating engine, we can factor common boilerplate out of +our page templates into reusable components. One method for doing this is +template inheritance via blocks. -Once we're done with the ``edit.pt`` template, it will look a lot like -the below: +- We have defined two placeholders in the layout template where a child + template can override the content. These blocks are named ``subtitle`` (line + 11) and ``content`` (line 36). +- Please refer to the Jinja2_ documentation for more information about template + inheritance. -.. literalinclude:: src/views/tutorial/templates/edit.pt - :language: xml -Static Assets -------------- +The ``view.jinja2`` template +---------------------------- -Our templates name a single static asset named ``pylons.css``. We don't need -to create this file within our package's ``static`` directory because it was -provided at the time we created the project. 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/static/pylons.css>`_. +Create ``tutorial/templates/view.jinja2`` and add the following content: -This CSS file will be accessed via -e.g. ``http://localhost:6543/static/pylons.css`` by virtue of the call to -``add_static_view`` directive we've made in the ``__init__.py`` file. Any -number and type of static assets can be placed in this directory (or -subdirectories) and are just referred to by URL or by using the convenience -method ``static_url`` -e.g. ``request.static_url('{{package}}:static/foo.css')`` within templates. +.. literalinclude:: src/views/tutorial/templates/view.jinja2 + :linenos: + :language: html -Mapping Views to URLs in ``__init__.py`` -======================================== +This template is used by ``view_page()`` for displaying a single wiki page. -The ``__init__.py`` file contains -:meth:`pyramid.config.Configurator.add_view` calls which serve to map -routes via :term:`url dispatch` to views. First, we’ll get rid of the -existing route created by the template using the name ``'home'``. It’s only an -example and isn’t relevant to our application. +- We begin by extending the ``layout.jinja2`` template defined above, which + provides the skeleton of the page (line 1). +- We override the ``subtitle`` block from the base layout, inserting the page + name into the page's title (line 3). +- We override the ``content`` block from the base layout to insert our markup + into the body (lines 5-18). +- We use a variable that is replaced with the ``content`` value provided by the + view (line 6). ``content`` contains HTML, so the ``|safe`` filter is used to + prevent escaping it (e.g., changing ">" to ">"). +- We create a link that points at the "edit" URL, which when clicked invokes + the ``edit_page`` view for the requested page (line 9). -We then need to add four calls to ``add_route``. Note that the *ordering* of -these declarations is very important. ``route`` declarations are matched in -the order they're found in the ``__init__.py`` file. -#. Add a declaration which maps the pattern ``/`` (signifying the root URL) - to the route named ``view_wiki``. +The ``edit.jinja2`` template +---------------------------- -#. Add a declaration which maps the pattern ``/{pagename}`` to the route named - ``view_page``. This is the regular view for a page. +Create ``tutorial/templates/edit.jinja2`` and add the following content: -#. Add a declaration which maps the pattern ``/add_page/{pagename}`` to the - route named ``add_page``. This is the add view for a new page. +.. literalinclude:: src/views/tutorial/templates/edit.jinja2 + :linenos: + :emphasize-lines: 1,3,12,14,17 + :language: html -#. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the - route named ``edit_page``. This is the edit view for a page. +This template serves two use cases. It is used by ``add_page()`` and +``edit_page()`` for adding and editing a wiki page. It displays a page +containing a form and which provides the following: -After we've defined the routes for our application, we can register views -to handle the processing and rendering that needs to happen when each route is -requested. +- Again, we extend the ``layout.jinja2`` template, which provides the skeleton + of the page (line 1). +- Override the ``subtitle`` block to affect the ``<title>`` tag in the + ``head`` of the page (line 3). +- A 10-row by 60-column ``textarea`` field named ``body`` that is filled with + any existing page data when it is rendered (line 14). +- A submit button that has the name ``form.submitted`` (line 17). +- The form POSTs back to the ``save_url`` argument supplied by the view (line + 12). The view will use the ``body`` and ``form.submitted`` values. -#. Add a declaration which maps the ``view_wiki`` route to the view named - ``view_wiki`` in our ``views.py`` file. This is the :term:`default view` - for the wiki. -#. Add a declaration which maps the ``view_page`` route to the view named - ``view_page`` in our ``views.py`` file. +The ``404.jinja2`` template +--------------------------- -#. Add a declaration which maps the ``add_page`` route to the view named - ``add_page`` in our ``views.py`` file. +Replace ``tutorial/templates/404.jinja2`` with the following content: -#. Add a declaration which maps the ``edit_page`` route to the view named - ``edit_page`` in our ``views.py`` file. +.. literalinclude:: src/views/tutorial/templates/404.jinja2 + :linenos: + :language: html -As a result of our edits, the ``__init__.py`` file should look -something like so: +This template is linked from the ``notfound_view`` defined in +``tutorial/views/notfound.py`` as shown here: -.. literalinclude:: src/views/tutorial/__init__.py +.. literalinclude:: src/views/tutorial/views/notfound.py :linenos: + :emphasize-lines: 6 :language: python -Viewing the Application in a Browser +There are several important things to note about this configuration: + +- The ``notfound_view`` in the above snippet is called an + :term:`exception view`. For more information see + :ref:`special_exceptions_in_callables`. +- The ``notfound_view`` sets the response status to 404. It's possible + to affect the response object used by the renderer via + :ref:`request_response_attr`. +- The ``notfound_view`` is registered as an exception view and will be invoked + **only** if ``pyramid.httpexceptions.HTTPNotFound`` is raised as an + exception. This means it will not be invoked for any responses returned + from a view normally. For example, on line 27 of + ``tutorial/views/default.py`` the exception is raised which will trigger + the view. + +Finally, we may delete the ``tutorial/templates/mytemplate.jinja2`` template +that was provided by the ``alchemy`` scaffold, as we have created our own +templates for the wiki. + +.. note:: + + Our templates use a ``request`` object that none of our tutorial + views return in their dictionary. ``request`` is one of several names that + are available "by default" in a template when a template renderer is used. + See :ref:`renderer_system_values` for information about other names that + are available by default when a template is used as a renderer. + + +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 (See +:ref:`wiki2-start-the-application`). Launch a browser and visit +each of the following URLs, checking that the result is as expected: + +- http://localhost:6543/ 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 object. +- http://localhost:6543/FrontPage invokes 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. +- http://localhost:6543/FrontPage/edit_page invokes the ``edit_page`` view for + the ``FrontPage`` page object. -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser - invokes the edit view for the front page object. +- http://localhost:6543/add_page/SomePageName invokes the ``add_page`` view for + a page. If the page already exists, then it redirects the user to the + ``edit_page`` view for the page object. -- Visiting ``http://localhost:6543/add_page/SomePageName`` in a - browser invokes the add view for a page. +- http://localhost:6543/SomePageName/edit_page invokes the ``edit_page`` view + for an existing page, or generates an error if the page does not exist. -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. +- To generate an error, visit http://localhost:6543/foobars/edit_page which + will generate a ``NoResultFound: No row was found for one()`` error. You'll + see an interactive traceback facility provided by + :term:`pyramid_debugtoolbar`. +.. _jinja2: http://jinja.pocoo.org/ |
