Navigation

Table Of Contents

Previous topic

TurboGears 2 At A Glance

Next topic

A Movie Database (Models, Views, Controllers)

The TurboGears 2 Wiki Tutorial¶

Welcome! This tutorial will show you how to create a simple wiki with TurboGears 2. It is designed to be your first experience with TurboGears 2.

If you’re not familiar with the concept of a wiki you might want to check out the Wikipedia entry. Basically, a wiki is an easily-editable collaborative web content system that makes it trivial to link to pages and create new pages. Like other wiki systems, we are going to use CamelCase words to designate links to pages.

If you have trouble with this tutorial ask for help on the TurboGears discussion list, or on the IRC channel #turbogears. We’re a friendly bunch and, depending what time of day you post, you’ll get your answer in a few minutes to a few hours. If you search the mailing list or the web in general you’ll probably get your answer even faster. Please don’t post your problem reports as comments on this or any of the following pages of the tutorial. Comments are for suggestions for improvement of the docs, not for seeking support.

If you want to see the final version you can download a copy of the wiki code.

Setup¶

To go through this tutorial, you’ll need:

  1. Python 2.4, 2.5 or 2.6. Note that Mac OSX 10.5 (Leopard) comes with Python 2.5 pre-installed; for 10.4 and before, follow Macintosh in the above link.

  2. TurboGears 2.1.5 Standard Installation

  3. docutils 0.4 or later, which is used for the wiki’s formatting. docutils is not a required part of TurboGears, but is needed for this tutorial. Install it with:

    $ easy_install docutils
    

    When using easy_install it doesn’t matter what directory you’re in. If you don’t have easy_install you only need to run peak.telecommunity.com/dist/ez_setup.py from any directory.

  4. A web browser.

  5. Your favorite editor.

  6. Two command line windows (you only need one, but two is nicer).

  7. A database. Python 2.5 comes with sqlite, so if you have Python 2.5, don’t do anything (though you will need sqlite3.0+ if you want to browse the database from the command line). If you’re running Python 2.4, your best bet is sqlite 3.2+ with pysqlite 2.0+. Install it with:

    $ easy_install pysqlite
    
  8. Optional: If you’re not aware of it, you may also find the ipython shell to be helpful. It supports attribute tab completion for many objects (which can help you find the method you’re searching for) and can display contextual help if you append a question mark onto the end of an object or method. You can do the same in the standard shell with the dir() and help() functions, but ipython is more convenient. ipython has a number of other convenient features, like dropping into the debugger on an error; take a look at the ipython docs for more information. You can install it with:

    $ easy_install ipython
    

This tutorial doesn’t cover Python at all. Check the Python Documentation page for more coverage of Python.

Quickstart¶

TurboGears provides a suite of tools for working with projects by adding several commands to the Python command line tool paster. A few will be touched upon in this tutorial. (Check the Command Line Reference for a full listing.) The first tool you’ll need is quickstart, which initializes a TurboGears project. Go to a command line window and run the following command:

$ paster quickstart

You’ll be prompted for the name of the project (this is the pretty name that human beings would appreciate), and the name of the package (this is the less-pretty name that Python will like). Here’s what our choices for this tutorial look like:

$ paster quickstart
Enter project name: Wiki 20
Enter package name [wiki20]: wiki20
Do you need authentication and authorization in this project? [yes] no

We recommend you use the names given here: this documentation looks for files in directories based on these names.

Now paster will spit out a bunch of stuff:

Selected and implied templates:
  tg.devtools#turbogears2  TurboGears 2.1 Standard Quickstart Template

...etc...

reading manifest file 'Wiki_20.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'Wiki_20.egg-info/SOURCES.txt'

This creates a few files in a directory tree just below your current directory. You will notice that the quickstart created a directory without spaces for convenience: project name “Wiki 20” resulted in the directory name “Wiki-20”. Go in there and take a look around:

$ cd Wiki-20

Now to be able to run the project you will need to install it and its dependencies. This can be quickly achieved by running from inside the Wiki-20 directory:

$ python setup.py develop

Then paster provides a simple mechanism for running a TurboGears project. Again from the Wiki-20 directory, run this command:

$ paster serve --reload development.ini

The --reload flag means that changes that you make in the project will automatically cause the server to restart itself. This way you immediately see the results.

Point your browser to localhost:8080, and you’ll see a nice welcome page. You now have a working project! And you can access the project from within the python/ipython shell by typing:

$ paster shell development.ini

If ipython is installed within your virtual environment, it will be the default shell. Right now, we’re not going to do much with the shell, but you may find other tutorials which use it to add data to the database.

Controller And View¶

If you take a look at the code that quickstart created, you’ll see everything necessary to get up and running. Here, we’ll look at the two files directly involved in displaying this welcome page.

TurboGears follows the Model-View-Controller paradigm (a.k.a. “MVC”), as do most modern web frameworks like Rails, Django, Struts, etc.

  • Model: For a web application, the “model” refers to the way the

    data is stored. In theory, any object can be your model. In practice, since we’re in a database-driven world, your model will be based on a relational database. By default TurboGears 2 uses the powerful, flexible, and relatively easy-to-use SQLAlchemy object relational mapper to build your model and to talk to your database. We’ll look at this in a later section.

  • View: To minimize duplication of effort web frameworks use

    templating engines which allow you to create “template” files. These specify how a page will always look, with hooks where the templating engine can substitute information provided by your web application. TurboGears 2’s default templating engine is Genshi, although several other engines are supported out of the box and can be configured in your config/app_cfg.py file (see Templating Options)

  • Controller: The controller is the way that you tell your web

    application how to respond to events that arrive on the server. In a web application, an “event” usually means “visiting a page” or “pressing a submit button” and the response to an event usually consists of executing some code and displaying a new page.

Controller Code¶

Wiki-20/wiki20/controllers/root.py is the code that causes the welcome page to be produced. After the imports the first line of code creates our main controller class by inheriting from TurboGears’ BaseController:

class RootController(BaseController):

The TurboGears 2 controller is a simple object publishing system; you write controller methods and @expose() them to the web. In our case, there’s a single controller method called index. As you might guess, this name is not accidental; this becomes the default page you’ll get if you go to this URL without specifying a particular destination, just like you’ll end up at index.html on an ordinary web server if you don’t give a specific file name. You’ll also go to this page if you explicitly name it, with localhost:8080/index. We’ll see other controller methods later in the tutorial so this naming system will become clear.

The @expose() decorator tells TurboGears which template to use to render the page. Our @expose() specifies:

@expose('wiki20.templates.index')

This gives TurboGears the template to use, including the path information (the .html extension is implied). We’ll look at this file shortly.

Each controller method returns a dictionary, as you can see at the end of the index method. TG takes the key:value pairs in this dictionary and turns them into local variables that can be used in the template.

Displaying The Page¶

Wiki-20/wiki20/templates/index.html is the template specified by the @expose() decorator, so it formats what you view on the welcome screen. Look at the file; you’ll see that it’s standard XHTML with some simple namespaced attributes. This makes it very designer-friendly, and well-behaved design tools will respect all the Genshi attributes and tags. You can even open it directly in your browser.

Genshi directives are elements and/or attributes in the template that are usually prefixed with py:. They can affect how the template is rendered in a number of ways: Genshi provides directives for conditionals and looping, among others. We’ll see some simple Genshi directives in the sections on Editing pages and Adding views.

Next, we’ll set up our data model, and create a database.

Wiki Model and Database¶

quickstart produced a directory for our model in Wiki-20/wiki20/model/. This directory contains an __init__.py file, which makes that directory name into a python module (so you can use import model).

Since a wiki is basically a linked collection of pages, we’ll define a Page class as the name of our model. Create a new file called page.py in the Wiki-20/wiki20/model/ directory:

from sqlalchemy import *
from sqlalchemy.orm import mapper
from wiki20.model import metadata

# Database table definition
# See: www.sqlalchemy.org/docs/04/sqlexpression.html

pages_table = Table("pages", metadata,
    Column("id", Integer, primary_key=True),
    Column("pagename", Text, unique=True),
    Column("data", Text)
)

# Python class definition
class Page(object):
    def __init__(self, pagename, data):
       self.pagename = pagename
       self.data = data

# Mapper
# See: www.sqlalchemy.org/docs/04/mappers.html
page_mapper = mapper(Page, pages_table)

In order to easily use our model within the application, modify the Wiki-20/wiki20/model/__init__.py file to add Page and pages_table to the module. Add the following line at the end of the file:.

from wiki20.model.page import Page, pages_table

Warning

It’s very important that this line is at the end because pages_table requires the rest of the model to be initialized before it can be imported:

Let’s investigate our model a little more. The MetaData object is automatically created by the paste command inside the __init__.py file. It’s a “single point of truth” that keeps all the information necessary to connect to and use the database. It includes the location of the database, connection information and the tables that are in that database. When you pass the metadata object to the various objects in your project they initialize themselves using that metadata.

In this case, the metadata object configures itself using the development.ini file, which we’ll look at in the next section.

The SQLAlchemy Table object defines what a single table looks like in the database, and adds any necessary constraints (so, for example, even if your database doesn’t enforce uniqueness, SQLAlchemy will attempt to do so). The first argument in the Table constructor is the name of that table inside the database. Next is the aforementioned metadata object followed by the definitions for each Column object. As you can see, Column objects are defined in the same way that you define them within a database: name, type, and constraints.

The Table object provides the representation of a database table, but we want to just work with objects, so we create an extremely simple class to represent our objects within TurboGears. The above idiom is quite common: you create a very simple class like Page with nothing in it, and add all the interesting stuff using mapper(), which attaches the Table object to our class.

Note that it’s also possible to start with an existing database, but that’s a more advanced topic that we won’t cover in this tutorial. If you would like more information on how to do that, check out AutoGenerating Model Code with SQLAutocode.

Database Configuration¶

By default, projects created with quickstart are configured to use a very simple SQLite database (however, TurboGears 2 supports most popular databases). This configuration is controlled by the development.ini file in the root directory (Wiki-20, for our project).

Search down until you find the [app:main] section in development.ini, and then look for sqlalchemy.url. You should see this:

sqlalchemy.url = sqlite:///%(here)s/devdata.db

Turbogears will automatically replace the %(here)s variable with the parent directory of this file, so for our example it will produce sqlite:///Wiki-20/devdata.db. You won’t see the devdata.db file now because we haven’t yet initialized the database.

Initializing The Tables¶

Before you can use your database, you need to initialize it and add some data. There’s built in support for this in TurboGears using paster setup-app. The quickstart template gives you a basic template database setup inside the websetup/boostrap.py file which by default creates two users, one manager group and one manage permission:

We need to update the file to create our FrontPage data just before the DBSession.flush() command by adding:

page = model.Page("FrontPage", "initial data")
model.DBSession.add(page)

The resulting boostrap file will look like:

# -*- coding: utf-8 -*-
"""Setup the wiki20 application"""

import logging
from tg import config
from wiki20 import model

import transaction


def bootstrap(command, conf, vars):
    """Place any commands to setup wiki20 here"""

    # <websetup.bootstrap.before.auth
    from sqlalchemy.exc import IntegrityError
    try:
        u = model.User()
        u.user_name = u'manager'
        u.display_name = u'Example manager'
        u.email_address = u'manager@somedomain.com'
        u.password = u'managepass'
    
        model.DBSession.add(u)
    
        g = model.Group()
        g.group_name = u'managers'
        g.display_name = u'Managers Group'
    
        g.users.append(u)
    
        model.DBSession.add(g)
    
        p = model.Permission()
        p.permission_name = u'manage'
        p.description = u'This permission give an administrative right to the bearer'
        p.groups.append(g)
    
        model.DBSession.add(p)
    
        u1 = model.User()
        u1.user_name = u'editor'
        u1.display_name = u'Example editor'
        u1.email_address = u'editor@somedomain.com'
        u1.password = u'editpass'
    
        model.DBSession.add(u1)

        page = model.Page("FrontPage", "initial data")
        model.DBSession.add(page)

        model.DBSession.flush()
        transaction.commit()
    except IntegrityError:
        print 'Warning, there was a problem adding your auth data, it may have already been added:'
        import traceback
        print traceback.format_exc()
        transaction.abort()
        print 'Continuing with bootstrapping...'
        

    # <websetup.bootstrap.after.auth>

If you’re familiar with SQLAlchemy this should look pretty standard to you. One thing to note is that we use:

transaction.commit()

Where you’re used to seeing DBSession.commit() we use transaction.commit(). This calls the transaction manager which helps us to support cross database transactions, as well as transactions in non relational databases, but ultimately in the case of SQLAlchemy it calls DBSession.commit() just like you might if you were doing it directly.

Now run the paster setup-app command:

$ paster setup-app development.ini

You’ll see output, but you should not see error messages. At this point your database is created and has some initial data in it, which you can verify by looking at Wiki-20/devdata.db. The file should exist and have a nonzero size.

That takes care of the “M” in MVC. Next is the “C”: controllers.

Adding Controllers¶

Controllers are the code that figures out which page to display, what data to grab from the model, how to process it, and finally hands off that processed data to a template.

quickstart has already created some basic controller code for us at Wiki-20/wiki20/controllers/root.py. Here’s what it looks like now:

# -*- coding: utf-8 -*-
"""Main Controller"""

from tg import expose, flash, require, url, request, redirect
from tg.i18n import ugettext as _, lazy_ugettext as l_

from wiki20.lib.base import BaseController
from wiki20.model import DBSession, metadata
from wiki20.controllers.error import ErrorController
from wiki20.model import Page


__all__ = ['RootController']


class RootController(BaseController):
    """
    The root controller for the Wiki-20 application.
    
    All the other controllers and WSGI applications should be mounted on this
    controller. For example::
    
        panel = ControlPanelController()
        another_app = AnotherWSGIApplication()
    
    Keep in mind that WSGI applications shouldn't be mounted directly: They
    must be wrapped around with :class:`tg.controllers.WSGIAppController`.
    
    """
    
    error = ErrorController()

    @expose('wiki20.templates.index')
    def index(self):
        """Handle the front-page."""
        return dict(page='index')

    @expose('wiki20.templates.about')
    def about(self):
        """Handle the 'about' page."""
        return dict(page='about')

The first thing we need to do is uncomment the line that imports DBSession.

Next we must import the Page class from our model. At the end of the import block, add this line:

from wiki20.model.page import Page

Now we will change the template used to present the data, by changing the @expose('wiki20.templates.index') line to:

@expose('wiki20.templates.page')

This requires us to create a new template named page.html in the wiki20/templates directory; we’ll do this in the next section.

Now we must specify which page we want to see. To do this, add a parameter to the index() method. Change the line after the @expose decorator to:

def index(self, pagename="FrontPage"):

This tells the index() method to accept a parameter called pagename, with a default value of "FrontPage".

Now let’s get that page from our data model. Put this line in the body of index:

page = DBSession.query(Page).filter_by(pagename=pagename).one()

This line asks the SQLAlchemy database session object to run a query for records with a pagename column equal to the value of the pagename parameter passed to our controller method. The .one() method assures that there is only one returned result; normally a .query call returns a list of matching objects. We only want one page, so we use .one().

Finally, we need to return a dictionary containing the page we just looked up. When we say:

return dict(wikipage=page)

The returned dict will create a template variable called wikipage that will evaluate to the page object that we looked it up.

Here’s the whole file after incorporating the above modifications:

# -*- coding: utf-8 -*-
"""Main Controller"""

from tg import expose, flash, require, url, request, redirect
from tg.i18n import ugettext as _, lazy_ugettext as l_

from wiki20.lib.base import BaseController
from wiki20.model import DBSession, metadata
from wiki20.controllers.error import ErrorController
from wiki20.model import Page


__all__ = ['RootController']


class RootController(BaseController):
    """
    The root controller for the Wiki-20 application.
    
    All the other controllers and WSGI applications should be mounted on this
    controller. For example::
    
        panel = ControlPanelController()
        another_app = AnotherWSGIApplication()
    
    Keep in mind that WSGI applications shouldn't be mounted directly: They
    must be wrapped around with :class:`tg.controllers.WSGIAppController`.
    
    """
    
    error = ErrorController()

    @expose('wiki20.templates.page')
    def index(self



gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.