Chapter 8: Starting the SimpleSite Tutorial
+++++++++++++++++++++++++++++++++++++++++++
.. note ::
You can download the source code for this chapter from http://www.apress.com.
.. index ::
single: Chap 8-Starting the Simple Site Tutorial
You’ve now learned about many of the most important components of an application. The previous chapters have been fairly detailed, so don’t worry if you haven’t understood everything you’ve read or if you’ve skipped over one or two sections. Once you have used Pylons a little more, you will appreciate having all the information on a particular topic in one place, even if it seems a lot to take in on the first reading.
.. index ::
single: web site example
In this chapter, I’ll recap many of the important points as I show how to create a simple web site from scratch so that you can see how all the components you’ve learned about in the preceding chapters fit together. The example will be created in a way that will also make it a really good basis for your own Pylons projects.
You’ll use SQLAlchemy to store the individual pages in the site in such a way that users can add, edit, or remove them so that the application behaves like a simple wiki. Each of the pages will also support comments and tags, so you can use some of the knowledge gained in the previous chapter to help you create the application. You’ll also use FormEncode and HTML Fill to handle the forms.
Later in the book you’ll return to SimpleSite and add authentication and authorization facilities, and you’ll add a navigation hierarchy so that pages can be grouped into sections at different URLs. You’ll implement some JavaScript to animate messages and use some Ajax to populate one of the form fields. The application will also have a navigation hierarchy so that you can see how to create navigation components such as breadcrumbs, tabs, and navigation menus using Mako.
.. index ::
single: finished, with customized front page; SimpleSite example
Figure 8-1 shows what the finished application will look like after the SimpleSite tutorial chapters (this chapter, Chapter 14, part of Chapter 15, and Chapter 19).
.. figure :: 9349f0801.png
:target: _images/9349f0801.png
:scale: 70
:alt: Figure 8-1. The finished wiki with a customized front page
Figure 8-1. The finished wiki with a customized front page
Getting Started with SimpleSite
===============================
.. index ::
single: creating new project; SimpleSite example
The first thing you need to do is create a new Pylons project called ``SimpleSite``. You’ll remember from Chapter 3 that the command to do this is as follows:
::
$ paster create --template=pylons SimpleSite
You’ll use Mako and SQLAlchemy in this chapter, so in particular note that you need to answer ``True`` to the SQLAlchemy question so that SQLAlchemy support is included for you:
::
Selected and implied templates:
Pylons#pylons Pylons application template
Variables:
egg: SimpleSite
package: simplesite
project: SimpleSite
Enter template_engine (mako/genshi/jinja/etc: Template language) ['mako']:
Enter sqlalchemy (True/False: Include SQLAlchemy 0.4 configuration) [False]: True
Creating template pylons
All these options are configurable later, but it is easier to have Pylons configure them for you when you create the project.
.. index ::
single: starting; server
Once the application template has been created, start the server, and see what you have:
::
$ cd SimpleSite
$ paster serve --reload development.ini
.. note ::
Notice that the server is started with the ``--reload`` switch. You’ll remember that this means any changes you make to the code will cause the server to restart so that your changes are immediately available for you to test in the web browser.
.. index ::
single: creating new project; SimpleSite example
If you visit http://127.0.0.1:5000, you will see the standard Pylons introduction page served from the application’s ``public/index.html`` file as before.
.. index ::
single: description of; cascade object
Pylons looks for resources in the order applications are specified in the cascade object in ``config/middleware.py``. You’ll learn more about the cascade object in Chapter 17, but it causes static files such as the introduction page to be served in preference to content generated by your Pylons controllers for the same URL. You will want the SimpleSite controllers to handle the site’s home page, so remove the welcome page HTML file:
::
$ cd simplesite
$ rm public/index.html
If you now refresh the page, the Pylons built-in error document support will kick in and display a 404 Not Found page to tell you that the URL requested could not be matched by Pylons.
.. index ::
single: customizing; SimpleSite example
You’ll now customize the controller so that it can display pages. Each of the pages is going to have its own ID, which the controller will obtain from the URL. Here are some example URLs that the controller will handle:
::
/page/view/1
/page/view/2
... etc
/page/view/10
You’ll also recall that Pylons comes with a routing system named Routes, which you saw in Chapter 3 and will learn about in detail in the next chapter. The default Routes setup analyzes the URL requested by the browser to find the controller, action, and ID. This means that to handle the URLs, you simply need a controller named ``page`` that has an action named ``view`` and that takes a parameter named ``id``.
.. index ::
single: creating; SimpleSite example
Let’s create the page controller. Once again, Pylons comes with a tool to help with this in the form of a plug-in to Paste. You create the controller like this (notice that the command doesn’t include the ``create`` part, which is used when creating a project template):
::
$ paster controller page
Creating /Users/james/Desktop/SimpleSite/simplesite/controllers/page.py
Creating /Users/james/Desktop/SimpleSite/simplesite/tests/functional/test_page.py
This creates two files—one for any tests you are going to add for this controller (see Chapter 12) and the other for the controller itself. The command would also add these files to Subversion automatically if your Pylons application were already being managed in a Subversion repository.
.. index ::
single: controllers/page.py file; listings
The ``controllers/page.py`` file that is added looks like this:
::
import logging
from pylons import request, response, session, tmpl_context as c
from pylons.controllers.util import abort, redirect_to
from simplesite.lib.base import BaseController, render
#import simplesite.model as model
log = logging.getLogger(__name__)
class PageController(BaseController):
def index(self):
# Return a rendered template
# return render('/template.mako')
# or, Return a response
return 'Hello World'
This is a skeleton controller that you’ll customize to handle pages. The first few lines import some of the useful objects described in Chapter 3 so that they are ready for you to use. The controller itself has one action named ``index()``, which simply returns a ``Hello World`` message.
Replace the ``index()`` action with a ``view()`` action that looks like this:
::
def view(self, id):
return "Page goes here"
The Paste HTTP server should reload when you save the change (as long as you used the ``--reload`` option), so you can now visit the URL http://localhost:5000/page/view/1. You should see the message ``Page goes here``.
.. index ::
single: creating; SimpleSite example
The page isn’t very exciting so far and isn’t even HTML, so now I’ll cover how to create some templates to generate real HTML pages.
Exploring the Template Structure
================================
.. index ::
single: template structure; SimpleSite example
Most web sites have the following features:
* Header and footer regions
* Sign-in and sign-out regions
* Top-level navigation tabs
* Second-level navigation tabs
* A page heading
* Breadcrumbs
* Content
* A head region for extra CSS and JavaScript
The SimpleSite application will need all these features too, so the template structure needs to be able to provide them.
You’ll remember from Chapter 5 that Pylons uses the Mako templating language by default, although as is the case with most aspects of Pylons, you are free to deviate from the default if you prefer. This is particularly useful if you are building a Pylons application to integrate with legacy code, but since you are creating a new application here, you are going to use Mako.
.. index ::
single: inheritance chain features; Mako templating language
Because you are going to need a few templates that will all look similar, you can take advantage of Mako’s inheritance chain features you learned about in Chapter 5 and use a single base template for all the different pages. You’ll also need to create some derived templates and some templates containing the navigation components.
You’ll structure these templates as follows:
``templates/base``
All the base templates.
``templates/derived``
All the templates that are derived from any of the base templates. There is likely to be a subdirectory for every controller you create. Since Pylons has already created an ``error`` controller, you’ll create a subdirectory for it, and you’ll need a subdirectory for the ``page`` controller templates too.
``templates/derived/error``
Templates for the ``error`` controller to render error documents.
``templates/component``
Any components that are used in multiple templates.
.. index ::
single: template structure; SimpleSite example
This structure is useful because it keeps templates that serve different purposes in different directories. If you are following along by creating the application yourself, you should create these directories now.
.. index ::
single: creating; base template
Let’s start by creating the base template. Save the following template as ``templates/base/index.html``:
::
## -*- coding: utf-8 -*-
${self.title()}
${self.head()}
${self.header()}
${self.tabs()}
${self.menu()}
${self.heading()}
${self.breadcrumbs()}
${next.body()}
${self.footer()}
<%def name="title()">SimpleSite%def>
<%def name="head()">%def>
<%def name="header()">%def>
<%def name="tabs()">%def>
<%def name="menu()">%def>
<%def name="heading()">${c.heading or 'No Title'}
%def>
<%def name="breadcrumbs()">%def>
<%def name="footer()">Top ^
%def>
.. index ::
single: template structure; SimpleSite example
This template should be fairly self-explanatory. It is a simple HTML document with eight defs defined. Each of the calls to ``${self.somedef()}`` will execute the def using either the definition in this base template or the definition in the template that inherits from it. The ``${next.body()}`` call will be replaced with the body of the template that inherits this one.
You’ll also see that the ``header()`` and ``footer()`` defs already contain some HTML, allowing the user to quickly click to the top of the page. The ``title()`` def contains some code that will set the title to SimpleSite, and the ``heading()`` def will obtain its content from the value of ``c.heading`` or will just use ``'No title'`` if no heading has been set.
``## -*- coding: utf-8 -*-`` has been added at the top of the file so that Unicode characters can be used when the template is saved as UTF-8 (you’ll learn more about Unicode in Chapter 10).
.. note ::
If you prefer using the ``strict_c`` option to ensure that accessing an attribute of the template context global raises an exception if that attribute doesn’t exist, you should change the content of the ``heading`` def to look like this:
::
${hasattr(c, 'heading') and c.heading or 'No Title'}
This ensures that the ``heading`` attribute exists before testing its value.
.. index ::
single: for SimpleSite example; view.html template
Now that the base template is in place, you can start creating the templates for the web site content. Create a new template in the ``templates/derived/page`` directory called ``view.html``. This will be the template used by the ``page`` controller’s ``view`` action. Add the following content to it:
::
<%inherit file="/base/index.html"/>
${c.content}
You’ll now need to update the ``view()`` action in the controller to use this template:
::
def view(self, id):
c.title = 'Greetings'
c.heading = 'Sample Page'
c.content = "This is page %s"%id
return render('/derived/page/view.html')
.. index ::
single: template structure; SimpleSite example
Now when you visit http://localhost:5000/page/view/1, you should see the page shown in Figure 8-2.
.. figure :: 9349f0802.png
:target: _images/9349f0802.png
:scale: 70
:alt: Figure 8-2. A basic page being rendered from a template
Figure 8-2. A basic page being rendered from a template
Using SQLAlchemy in Pylons
==========================
.. index ::
single: overview of; SimpleSite example
Now that you have the project’s templates and controller set up, you need to start thinking about the model. As you’ll recall from Chapter 1, Pylons is set up to use a Model View Controller architecture, and SQLAlchemy is what is most often used as the model. Chapter 7 explained SQLAlchemy in detail, but now you’ll see how to apply that theory to a real Pylons project. If you haven't already done so you'll need to install SQLAlchemy 0.5 which is the version used in this book. You can do so with this command:
::
$ easy_install "SQLAlchemy>=0.5,<=0.5.99"
Let’s begin by setting up the *engine*. Open your project’s ``config/environment.py`` file, and after ``from pylons import config``, you’ll see the following:
::
from sqlalchemy import engine_from_config
.. index ::
single: engine_from_config() function
You learned about engines in Chapter 7 when you created one directly. Pylons uses the ``engine_from_config()`` function to create an engine from configuration options in your project’s config file instead. It looks for any options starting with ``sqlalchemy.`` in the ``[app:main]`` section of your ``development.ini`` config file and creates the engine based on these options. This means that all you need to do to configure SQLAlchemy is set the correct options in the config file.
Configuring the Engine
----------------------
.. index ::
single: engine, configuring; SimpleSite example
The main configuration option you need is ``sqlalchemy.url``. This specifies the data source name (DSN) for your database and takes the format ``engine://username:password@host:port/database?foo=bar``, where ``foo=bar`` sets an engine-specific argument named ``foo`` with the value ``bar``. Once again, this is the same setting you learned about in Chapter 7 during the discussion of engines.
For SQLite, you might use an option like this to specify a database in the same directory as the config file:
::
sqlalchemy.url = sqlite:///%(here)s/databasefile.sqlite
Here ``databasefile.sqlite`` is the SQLite database file, and ``%(here)s`` represents the directory containing the ``development.ini`` file. If you’re using an absolute path, use four slashes after the colon: ``sqlite:////var/lib/myapp/databasefile.sqlite``. The example has three slashes because the value of ``%(here)s`` always starts with a slash on Unix-like platforms. Windows users should use four slashes because ``%(here)s`` on Windows starts with the drive letter.
For MySQL, you might use these options:
::
sqlalchemy.url = mysql://username:password@host:port/database
sqlalchemy.pool_recycle = 3600
Enter your username, your password, the host, the port number (usually 3306), and the name of your database.
.. index ::
single: pool_recycle option (MySQL)
It’s important to set the ``pool_recycle`` option for MySQL to prevent “MySQL server has gone away” errors. This is because MySQL automatically closes idle database connections without informing the application. Setting the connection lifetime to 3600 seconds (1 hour) ensures that the connections will be expired and re-created before MySQL notices they’re idle. ``pool_recycle`` is one of many engine options SQLAlchemy supports. Some of the others are listed at http://www.sqlalchemy.org/docs/05/dbengine.html#dbengine_options and are used in the same way, prefixing the option name with ``sqlalchemy.``.
For PostgreSQL, your DSN will usually look like this:
::
sqlalchemy.url = postgres://username:password@host:port/database
The options are the same as for MySQL, but you don’t generally need to use the ``pool_recycle`` option with PostgreSQL.
.. index ::
single: engine, configuring; SimpleSite example
By default, Pylons sets up the DSN ``sqlalchemy.url = sqlite:///%(here)s/development.db``, so if you don’t change it, this is what will be used. Whichever DSN you use, you still need to make sure you have installed SQLAlchemy along with the appropriate DB-API driver you want to use; otherwise, SQLAlchemy won’t be able to connect to the database. Again, see Chapter 7 for more information.
Creating the Model
------------------
.. index ::
single: creating; SimpleSite example
Once you have configured the engine, it is time to configure the model. This is easy to do; you simply add all your classes, tables, and mappers to the end of ``model/__init__.py``. The SimpleSite application will use the structures you worked through in the previous chapter, so I won’t discuss them in detail here.
.. index ::
single: model._init_.py; listings
Change ``model.__init__.py`` to look like this:
::
"""The application's model objects"""
import sqlalchemy as sa
from sqlalchemy import orm
from simplesite.model import meta
# Add these two imports:
import datetime
from sqlalchemy import schema, types
def init_model(engine):
"""Call me before using any of the tables or classes in the model"""
## Reflected tables must be defined and mapped here
#global reflected_table
#reflected_table = sa.Table("Reflected", meta.metadata, autoload=True,
# autoload_with=engine)
#orm.mapper(Reflected, reflected_table)
# We are using SQLAlchemy 0.5 so transactional=True is replaced by
# autocommit=False
sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine)
meta.engine = engine
meta.Session = orm.scoped_session(sm)
# Replace the rest of the file with the model objects we created in
# chapter 7
def now():
return datetime.datetime.now()
page_table = schema.Table('page', meta.metadata,
schema.Column('id', types.Integer,
schema.Sequence('page_seq_id', optional=True), primary_key=True),
schema.Column('content', types.Text(), nullable=False),
schema.Column('posted', types.DateTime(), default=now),
schema.Column('title', types.Unicode(255), default=u'Untitled Page'),
schema.Column('heading', types.Unicode(255)),
)
comment_table = schema.Table('comment', meta.metadata,
schema.Column('id', types.Integer,
schema.Sequence('comment_seq_id', optional=True), primary_key=True),
schema.Column('pageid', types.Integer,
schema.ForeignKey('page.id'), nullable=False),
schema.Column('content', types.Text(), default=u''),
schema.Column('name', types.Unicode(255)),
schema.Column('email', types.Unicode(255), nullable=False),
schema.Column('created', types.TIMESTAMP(), default=now()),
)
pagetag_table = schema.Table('pagetag', meta.metadata,
schema.Column('id', types.Integer,
schema.Sequence('pagetag_seq_id', optional=True), primary_key=True),
schema.Column('pageid', types.Integer, schema.ForeignKey('page.id')),
schema.Column('tagid', types.Integer, schema.ForeignKey('tag.id')),
)
tag_table = schema.Table('tag', meta.metadata,
schema.Column('id', types.Integer,
schema.Sequence('tag_seq_id', optional=True), primary_key=True),
schema.Column('name', types.Unicode(20), nullable=False, unique=True),
)
class Page(object):
pass
class Comment(object):
pass
class Tag(object):
pass
orm.mapper(Comment, comment_table)
orm.mapper(Tag, tag_table)
orm.mapper(Page, page_table, properties={
'comments':orm.relation(Comment, backref='page'),
'tags':orm.relation(Tag, secondary=pagetag_table)
})
.. warning ::
The book was written against Pylons 0.9.7rc2 but the actual release of Pylons 0.9.7 uses SQLAlchemy 0.5 by default. This means the ``init_model()`` function in your project might look slightly different to the version shown above. You should keep the version generated by Pylons rather than using the version above. In Pylons 0.9.7 the ``sessionmaker()`` call is still made, but it happens in your project's ``model/meta.py`` file instead of ``model/__init__.py``.
.. index ::
single: creating; SimpleSite example
As I mentioned, this will look very familiar because it is a similar setup to the one you used in the previous chapter. There are some points to note about this code, though:
.. index ::
single: MetaData object
* The ``MetaData`` object Pylons uses is defined in ``model/meta.py`` so is accessed here as ``meta.metadata``, whereas in the previous chapter the examples just used ``metadata``.
.. index ::
single: init_model() function
* Pylons generated the ``init_model()`` function when the project was created. It gets called after the engine has been created each time your application starts from ``config/environment.py`` to connect the model to the database.
.. caution ::
.. index ::
single: creating; SimpleSite example
Pylons generates a project to use SQLAlchemy 0.4, but many users will want to use the newer SQLAlchemy 0.5 described in Chapter 7. They are very similar, but the ``transactional=True`` argument to ``orm.sessionmaker()`` in ``init_model()`` is deprecated. Instead, you should specify ``autocommit=False``. This has the same behavior but will not generate a deprecation warning.
Creating the Database Tables
----------------------------
.. index ::
single: creating; SimpleSite example
Pylons has a built-in facility to allow users who download your application to easily set it up. The process is described in detail in the later SimpleSite tutorial chapters (Chapters 14 and 19), but you’ll use it here too so that you can easily set up the tables you need.
.. index ::
single: paster setup-app command
The idea is that users of your application can simply run ``paster setup-app development.ini`` to have the database tables and any initial data created for them automatically. You can set up this facility through your project’s ``websetup.py`` file.
The default ``websetup.py`` file for a SQLAlchemy project looks like this:
::
"""Setup the SimpleSite application"""
import logging
from simplesite.config.environment import load_environment
log = logging.getLogger(__name__)
def setup_app(command, conf, vars):
"""Place any commands to setup simplesite here"""
load_environment(conf.global_conf, conf.local_conf)
# Load the models
from simplesite.model import meta
meta.metadata.bind = meta.engine
# Create the tables if they aren't there already
meta.metadata.create_all(checkfirst=True)
When the ``paster setup-app`` command is run, Pylons calls the ``setup_app()`` function and loads the Pylons environment, setting up a SQLAlchemy engine as it does so. It then binds the engine to the metadata and calls ``metadata.create_all(checkfirst=True)`` to create any tables that don’t already exist.
.. index ::
single: creating; SimpleSite example
Binding the metadata in this way connects the engine to the ``metadata`` object used by the classes, tables, and mappers in the model. You can think of it as a shortcut to set up the model without the complexity of a session.
.. index ::
single: setup_app() function
You’ll now customize the default code so that it also adds a home page to the database. Add the following to the end of the ``setup_app()`` function:
::
log.info("Adding homepage...")
page = model.Page()
page.title=u'Home Page'
page.content = u'Welcome to the SimpleSite home page.'
meta.Session.add(page)
meta.Session.commit()
log.info("Successfully set up.")
You’ll also need to add this import at the top:
::
from simplesite import model
.. note ::
The recommended way of adding an object to the SQLAlchemy session in SQLAlchemy 0.4 was to call the session's ``save()`` method. The session in SQLAlchemy 0.5 provides the ``add()`` method instead. Since this book covers SQLAlchemy 0.5, the examples will use the ``add()`` method.
.. index ::
single: development mode; setuptools package
To test this functionality, you should first make SimpleSite available in your virtual Python environment. Rather than installing SimpleSite as you would install a normal Python package, you can instead use a special feature of the ``setuptools`` package used by Easy Install called *development mode*. This has the effect of making other Python packages treat your Pylons project’s source directory as if it were an installed Python egg even though it is actually just a directory structure in the filesystem.
It works by adding a ``SimpleSite.egg-link`` file to your virtual Python installation's site-packages directory containing the path to the application. It also adds an entry to the ``easy_install.pth`` file so that the project is added to the Python path. This is very handy because it means that any changes you make to the SimpleSite project are instantly available without you having to create and install the package every time you make a change. Set up your project in development mode by entering this command:
::
$ python setup.py develop
If you haven’t specified ``sqlalchemy.url`` in your ``development.ini`` config file, you should do so now. Then you are ready to run the ``paster setup-app`` command to set up your tables:
::
$ paster setup-app development.ini
.. index ::
single: creating; SimpleSite example
If all goes well, you should see quite a lot of log output produced, ending with the following lines and the message that everything was successfully set up:
::
12:37:36,429 INFO [simplesite.websetup] Adding homepage...
12:37:36,446 INFO [sqlalchemy.engine.base.Engine.0x..70] BEGIN
12:37:36,449 INFO [sqlalchemy.engine.base.Engine.0x..70] INSERT INTO page (content, posted, title, heading) VALUES (?, ?, ?, ?)
12:37:36,449 INFO [sqlalchemy.engine.base.Engine.0x..70] [u'Welcome to the SimpleSite home page.', '2008-09-12 12:37:36.449094', u'Home Page', None]
12:37:36,453 INFO [sqlalchemy.engine.base.Engine.0x..70] COMMIT
12:37:36,460 INFO [simplesite.websetup] Successfully set up.
Querying Data
-------------
.. index ::
single: creating; SimpleSite example
Now that the home page data is in SQLAlchemy, you need to update the ``view()`` action to use it. You’ll recall that you query SQLAlchemy using a *query* object. Here’s how:
::
def view(self, id):
page_q = model.meta.Session.query(model.Page)
c.page = page_q.get(int(id))
return render('/derived/page/view.html')
Notice that since the heading is optional, you are using the title as the heading if the heading is empty.
To use this, you need to uncomment the following line at the top of the controller file:
::
#import simplesite.model as model
Now add some more imports the controller will need:
::
import simplesite.model.meta as meta
import simplesite.lib.helpers as h
Finally, you’ll need to update the ``templates/derived/page/view.html`` template to use the ``page`` object:
::
<%inherit file="/base/index.html"/>
<%def name="title()">${c.page.title}%def>
<%def name="heading()">${c.page.heading or c.page.title}
%def>
${c.page.content}
The new heading def will display the heading if there is one and the title if no heading has been set.
.. index ::
single: querying data; SimpleSite example
Visit http://localhost:5000/page/view/1 again, and you should see the data loaded from the database (Figure 8-3). You’ll need to restart the server if you stopped it to run ``paster setup-app``.
.. figure :: 9349f0803.png
:target: _images/9349f0803.png
:scale: 70
:alt: Figure 8-3. A basic page being rendered with data from the database
Figure 8-3. A basic page being rendered with data from the database
.. caution ::
.. index ::
single: separating from models; templates
single: querying data; SimpleSite example
Generally speaking, it is a good idea to try to keep your model and templates as separate as possible. You should always perform all model operations in your controller and never in a template. This is so that you, and other people working on your project, always know where changes to your model are happening so that code is easy to maintain.
To avoid the risk of a lazy programmer performing operations on the model from within a template, you might prefer not to pass model objects directly to the controller and instead pass the useful attributes. In this case, rather than passing ``c.page``, you might instead pass ``c.title``, ``c.content``, and other useful attributes rather than the whole object. In this book, I’ll be strict about only using model objects for read-only access in templates, so it is OK to pass them in directly.
Understanding the Role of the Base Controller
---------------------------------------------
.. index ::
single: base controller role; SimpleSite example
single: BaseController class
Every Pylons project has a base controller in the project’s ``lib/base.py`` file. All your project’s controllers are by default derived from the ``BaseController`` class, so this means that if you want to change the behavior of all the controllers in your project, you can make changes to the base controller. Of course, if you want to derive your controllers directly from ``pylons.controllers.WSGIController``, you are free to do so too.
.. index ::
single: base controller for SimpleSite; listings
The SimpleSite base controller looks like this:
::
"""The base Controller API
Provides the BaseController class for subclassing.
"""
from pylons.controllers import WSGIController
from pylons.templating import render_mako as render
from simplesite.model import meta
class BaseController(WSGIController):
def __call__(self, environ, start_response):
"""Invoke the Controller"""
# WSGIController.__call__ dispatches to the Controller method
# the request is routed to. This routing information is
# available in environ['pylons.routes_dict']
try:
return WSGIController.__call__(self, environ, start_response)
finally:
meta.Session.remove()
.. index ::
single: base controller role; SimpleSite example
All the individual controllers that you create with the ``paster controller`` command also import the ``render()`` function from this file, so if you want to change the templating language for all your controllers, you can change the ``render()`` import in this file, and all the other controllers will automatically use the new function. Of course, you will have to set up the templating language in your project’s ``config/environment.py`` file too. This was described in Chapter 5.
Using a SQLAlchemy Session in Pylons
------------------------------------
.. index ::
single: SQLAlchemy sessions, using; SimpleSite example
You’ve learned about SQLAlchemy sessions in some detail in Chapter 7, but now let’s take a brief look at how sessions are used in Pylons.
The relevant lines to set up the session are found in your ``model/__init__.py`` file’s ``init_model()`` function and look like this:
::
sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine)
meta.engine = engine
meta.Session = orm.scoped_session(sm)
.. index ::
single: meta.Session class
The ``meta.Session`` class created here acts as a thread-safe wrapper around ordinary SQLAlchemy session objects so that data from one Pylons request doesn’t get mixed up with data from other requests in a multithreaded environment. Calling ``meta.Session()`` returns the thread’s actual session object, but you wouldn’t normally need to access it in this way because when you call the class’s ``meta.Session.query()`` method, it will automatically return a query object from the correct hidden session object. You can then use the query object as normal to fetch data from the database.
Since a new session is created for each request, it is important that sessions that are no longer needed are removed. Because you chose to use a SQLAlchemy setup when you created the project, Pylons has added a line to remove any SQLAlchemy session at the end of the ``BaseController`` class’s ``__call__()`` method:
::
meta.Session.remove()
.. index ::
single: SQLAlchemy sessions, using; SimpleSite example
This simply removes the session once your controller has returned the data for the response.
Updating the Controller to Support Editing Pages
================================================
.. index ::
single: updating to support editing pages; SimpleSite example
To quickly recap, you’ve set up the model, configured the database engine, set up the templates, and written an action to view pages based on their IDs. Next, you need to write the application logic and create the forms to enable a user to create, list, add, edit, and delete pages.
You’ll need the following actions:
``view(self, id)``
Displays a page
``new(self)``
Displays a form to create a new page
``create(self)``
Saves the information submitted from ``new()`` and redirects to ``view()``
``edit(self, id)``
Displays a form for editing the page ``id``
``save(self, id)``
Saves the page ``id`` and redirects to ``view()``
``list(self)``
Lists all pages
``delete(self, id)``
Deletes a page
This structure forms a good basis for a large number of the cases you are likely to program with a relational database.
view()
------
.. index ::
single: view() method; SimpleSite example
The existing ``view()`` method correctly displays a page but currently raises an error if you try to display a page that doesn’t exist. This is because ``page_q.get()`` returns None when the page doesn't exist so that ``c.page`` is set to None. This causes an exception in the template when ``c.page.title`` is accessed.
.. index ::
single: abort() function
Also, if a URL cannot be found, the HTTP specification says that a 404 page should be returned. You can use the ``abort()`` function to immediately stop the request and trigger the Pylons error documents middleware to display a 404 Not Found page. You should also display a 404 page if the user doesn’t specify a page ID. Here’s what the updated code looks like. Notice that the action arguments have been changed so that ``id`` is now optional.
::
def view(self, id=None):
if id is None:
abort(404)
page_q = meta.Session.query(model.Page)
c.page = page_q.get(int(id))
if c.page is None:
abort(404)
return render('/derived/page/view.html')
.. tip ::
When you are testing a condition that involves ``None`` in Python, you should use the ``is`` operator rather than the ``==`` operator.
new()
-----
.. index ::
single: new() method; SimpleSite example
You also need a template for pages that don’t already exist. It needs to display a form and submit it to the ``create()`` action to create the page. Most of the functionality to display the form will take place in the templates. Here’s what the action looks like; add this to the controller:
::
def new(self):
return render('/derived/page/new.html')
Thinking ahead, the ``edit()`` action will also display a form, and it is likely to have many of the same fields. You’ll therefore implement the fields in one template where they can be shared and create the form itself in another.
.. index ::
single: templates/derived/page/fields.html file; listings
First let’s create the fields in ``templates/derived/page/fields.html``. Here’s what the file looks like:
::
${h.field(
"Heading",
h.text(name='heading'),
required=False,
)}
${h.field(
"Title",
h.text(name='title'),
required=True,
field_desc = "Used as the heading too if you didn't specify one above"
)}
${h.field(
"Content",
h.textarea(name='content', rows=7, cols=40),
required=True,
field_desc = 'The text that will make up the body of the page'
)}
Remember that ``h`` is simply another name for your project’s ``simplesite.lib.helpers`` module and that it is available in both the templates and the controllers. In this case, you’re using a helper called ``field()`` that generates the HTML for a row in a table containing a label for a field and the field itself. It also allows you to specify ``field_desc``, which is a line of text that appears immediately below the HMTL field, or ``label_desc``, which is for text appearing immediately below the label. Specifying ``required=True`` adds an asterisk (``*``) to the start of the label, but it doesn’t affect how the controller handles whether fields are actually required.
.. index ::
single: new() method; SimpleSite example
To use the ``field`` helper, you need to first import it into the ``helpers`` module. You’ll also use ``form_start()`` and ``form_end()``, so let’s import them at the same time. At the top of ``lib/helpers.py``, add the following:
::
from formbuild.helpers import field
from formbuild import start_with_layout as form_start, end_with_layout as form_end
from webhelpers.html.tags import *
.. index ::
single: FormBuild package
single: FormBuild package; installing
Notice that you are using a package called FormBuild here. You could equally well code all your field structures in HTML, but FormBuild will help create a structure for you. FormBuild actually contains some extra functionality too, but you will use it only for its ``field()``, ``start_with_layout()``, and ``end_with_layout()`` helpers in the book. At some point in the future, these helpers are likely to be added to the Pylons WebHelpers package. Install FormBuild like this:
::
$ easy_install "FormBuild>=2.0,<2.99"
.. index ::
single: new() method; SimpleSite example
You should also add it as a dependency to your project by editing the ``setup.py`` file and adding FormBuild to the end of the ``install_requires`` argument, and updating the SQLAlchemy line to look like this:
::
install_requires=[
"Pylons>=0.9.7",
"SQLAlchemy>=0.5,<=0.5.99",
"Mako",
"FormBuild>=2.0,<2.99",
],
Next you need the form itself. Like the ``view.html`` page, this can be based on the ``/base/index.html`` page using Mako’s template inheritance. Here’s how ``/derived/page/new.html`` looks:
::
<%inherit file="/base/index.html" />
<%namespace file="fields.html" name="fields" import="*"/>
<%def name="heading()">
Create a New Page
%def>
${h.form_start(h.url_for(controller='page', action='create'), method="post")}
${fields.body()}
${h.field(field=h.submit(value="Create Page", name='submit'))}
${h.form_end()}
This template imports the defs in the ``fields.html`` file into the ``fields`` namespace. It then calls its ``body()`` def to render the form fields. ``h.form_start()`` and ``h.form_end()`` create the HTML ``