Writing Controller Methods

The nerve center of your TurboGears application is the controller. It ultimately handles all user actions, because every HTTP request arrives here first. The controller acts on the request and can call upon other TurboGears components (the template engines, database layers, etc.) as its logic directs.

When the TurboGears server receives an HTTP request, the requested URL is mapped as a call to your controller code located in controllers.py. Page names map to functions within the controller class.

For example:

URL Maps to
http://localhost:8080/index Root.index()
http://localhost:8080/mypage Root.mypage()

Quick Example

Suppose using paster quickstart you generate a TurboGears project named “HelloWorld”. Your default controller code would be created in the file HelloWorld/helloworld/controllers/root.py.

Modify the default controllers.py to read as follows:

"""Main Controller"""
from helloworld.lib.base import BaseController
from tg import expose, flash
#from tg import redirect, validate
#from helloworld.model import DBSession

class RootController(BaseController):

     @expose()
     def index(self):
         return "<h1>Hello World</h1>"

     @expose()
     def _default(self, *args, **kw):
         return "This page is not ready"

When you load the root URL http://localhost:8080/index in your web browser, you’ll see a page with the message “Hello World” on it. In addition, any of these URLs will return the same result.

Implementing A Catch-All Url Via The _default() Method

URLs not explicitly mapped to other methods of the controller will generally be directed to the method named _default(). With the above example, requesting any URL besides /index, for example http://localhost:8080/hello, will return the message “This page is not ready”.

Adding More Pages

When you are ready to add another page to your site, for example at the URL

http://localhost:8080/anotherpage

add another method to class RootController as follows:

@expose()
def anotherpage(self):
    return "<h1>There are more pages in my website</h1>"

Now, the URL /anotherpage will return:

There are more pages in my website

Line By Line Explanation

"""Main Controller"""
from helloworld.lib.base import BaseController
from tg import expose, flash
from tg.i18n import ugettext as _
#from tg import redirect, validate
#from helloworld.model import DBSession

First you need to import the required modules.

There’s a lot going on here, including some stuff for internationalization. But we’re going to gloss over some of that for now. The key thing to notice is that you are importing a BaseController, which your RootController must inherit from. If you’re particularly astute, you’ll have noticed that you import this BaseController from the lib module of your own project, and not from TurboGears.

TurboGears provides a base TGController which is imported in the lib folder of the current project (HelloWorld/helloworld/lib) so that you can modify it to suit the needs of your application. For example, you can define actions which will happen on every request, add parameters to every template call, and otherwise do what you need to the request on the way in, and on the way out.

The next thing to notice is that we are importing expose from tg.

BaseController classes and the expose decorator are the basis of TurboGears controllers. The @expose decorator declares that your method should be exposed to the web, and provides you with the ability to say how the results of the controller should be rendered.

The other imports are there in case you do internationalization, use the HTTP redirect function, validate inputs/outputs, or use the models.

class RootController(BaseController):

RootController is the required standard name for the RootController class of a TurboGears application and it should inherit from the BaseController class. It is thereby specified as the request handler class for the website’s root.

In TurboGears 2 the web site is represented by a tree of controller objects and their methods, and a TurboGears website always grows out from the RootController class.

def index(self):
    return "<h1>Hello World</h1>"

We’ll look at the methods of the RootController class next.

The index method is the start point of any TurboGears controller class. Each of the URLs

is mapped to the RootController.index() method.

If a URL is requested and does not map to a specific method, the _default() method of the controller class is called:

def _default(self):
    return "This page is not ready"

In this example, all pages except the three URLs listed above will map to the _default method.

As you can see from the examples, the response to a given URL is determined by the method it maps to.

@expose()

The @expose() seen before each controller method directs TurboGears controllers to make the method accessible through the web server. Methods in the controller class that are not “exposed” can not be called directly by requesting a URL from the server.

There is much more to @expose(). It will be our access to TurboGears sophisticated rendering features that we will explore shortly.

Are You Sure You Wanted To expose Strings All The Time?

As shown above, controller methods return the data of your website. So far, we have returned this data as literal strings. You could produce a whole site by returning only strings containing raw HTML from your controller methods, but it would be difficult to maintain, since Python code and HTML code would not be cleanly separated.

Expose + Template == Good

To enable a cleaner solution, data from your TurboGears controller can be returned as strings, or as a dictionary.

With @expose(), a dictionary can be passed from the controller to a template which fills in its placeholder keys with the dictionary values and then returns the filled template output to the browser.

Template Example

A simple template file called sample could be made like this:

<html>
  <head>
<title>TurboGears Templating Example</title>
  </head>
  <body>
      <h2>I just want to say that ${person} should be the next
        ${office} of the United States.</h2>
  </body>
</html>

The ${param} syntax in the template indicates some undetermined values to be filled.

We provide them by adding a method to the controller like this …

@expose(template="helloworld.templates.sample")
def example(self):
    mydata = {'person':'Tony Blair','office':'President'}
    return mydata

… then the following is made possible:

  • The web user goes to http://localhost:8080/example.

  • The example method is called.

  • The method example returns a Python dict.

  • @expose processes the dict through the template file named sample.html.

  • The dict values are substituted into the final web response.

  • The web user sees a marked up page saying:

    I just want to say that Tony Blair should be the next President of the United States.

Template files can thus house all markup information, maintaining clean separation from controller code.

SubControllers And The URL Hierarchy

Sometimes your web-app needs a URL structure that’s more than one level deep.

TurboGears provides for this by traversing the object hierarchy, to find a method that can handle your request.

To make a sub-controller, all you need to do is make your sub-controller inherit from the object class. However there’s a SubController class Controller in your project’s lib.base (HelloWorld/helloworld/lib/base.py) for you to use if you want a central place to add helper methods or other functionality to your SubControllers:

from lib.base import BaseController
from tg import redirect

class MovieController(BaseController):
    @expose()
    def index(self):
        redirect('list/')

    @expose()
    def list(self):
        return 'hello'

class RootController(BaseController):
    movie = MovieController()

With these in place, you can follow the link:

and you will be redirected to:

Unlike turbogears 1, going to http://localhost:8080/movie will not redirect you to http://localhost:8080/movie/list. This is due to some interesting bit about the way WSGI works. But it’s also the right thing to do from the perspective of URL joins. Because you didn’t have a trailing slash, there’s no way to know you meant to be in the movie directory, so redirection to relative URLs will be based on the last / in the URL. In this case the root of the site.

It’s easy enough to get around this, all you have to do is write your redirect like this:

redirect('/movie/list/')

Which provides the redirect method with an absolute path, and takes you exactly where you wanted to go, no matter where you came from.

Passing Parameters To The Controller

Now that you have the basic routing dispatch understood, you may be wondering how parameters are passed into the controller methods. After all, a framework would not be of much use unless it could accept data streams from the user.

TurboGears uses introspection to assign values to the arguments in your controller methods. This happens using the same duck-typing you may be familiar with if you are a frequent python programmer. Here is the basic approach:

  • The dispatcher gobbles up as much of the URL as it can to find the
    correct controller method associated with your request.
  • The remaining url items are then mapped to the parameters in the method.
  • If there are still remaining parameters they are mapped to *args in the method signature.
  • If there are named parameters, (as in a form request, or a GET request with parameters), they are mapped to the
    args which match their names, and if there are leftovers, they are placed in **kw.

Here is an example controller and a chart outlining the way urls are mapped to it’s methods:

class WikiController(TGController):

    def index(self):
        """returns a list of wiki pages"""
        ...

    def _default(self, *args):
        """returns one wikipage"""
        ...

    def create(self, title, text, author='anonymous', **kw):
        wikipage = Page(title=tile, text=text, author=author, tags=str(kw))
        DBSession.add(wikipage)

    def update(self, title, **kw):
        wikipage = DBSession.query(Page).get(title)
        for key, value in kw:
            setattr(wikipage, key, value)

    def delete(self, title):
        wikipage = DBSession.query(Page).get(title)
        DBSession.delete(wikipage)
URL Method Argument Assignments
/ index  
/NewPage _default args : [‘NewPage’]
/create/NewPage?text=More Information create text: ‘More Information’
title: ‘NewPage’
/update/NewPage?author=Lenny update kw: {‘author’:’Lenny’}
title: ‘NewPage’
/delete/NewPage delete title :’NewPage’

The parameters that are turned into arguments arrive in string format. It is a good idea to use Python’s type casting capabilities to change the arguments into the types the rest of your program expects. For instance, if you pass an integer ‘id’ into your function you might use id = int(id) to cast it into an int before usage. Another way to accomplish this feat is to use the @validate decorator, which is explained in FormEncode @validate, and TurboGears Validation

Ignore Unused Parameters

By default TurboGears2 will complain about parameters that the controller method was not expecting. If this is causing any issue as you need to share between all the urls a parameter that it is used by your javascript framework or for any other reason, you can use ignore_parameters option to have TurboGears2 ignore them. Just add the list of parameters to ignore in config/app_cfg.py:

base_config.ignore_parameters = ['timestamp', 'param_name']

You will still be able to access them from the tg.request object if you need them for any reason.

Differences Between Dispatch In TurboGears 1.x and 2.x

Here are the major differences in dispatch between CherryPy/Turbogears1 and TurboGears 2.

  • We have not yet implemented cherrypy’s mechanism that replaces dots in the URL with underscores when looking up a method name. If this feature is important to you let us know on the mailing list.
  • TurboGears2 implements a Quixote inspired lookup method which allows you to do customized dispatch at controller execution time.
  • TurboGears2 implements a new REST-based for resource-driven dispatch.
  • TurboGears2 provides an easy path for supporting mime-type extensions as part of dispatch.
  • Redirect does not know “where you are” in the object tree and move you on from there, it just joins the URL the user requested, with the absolute or relative URL you provide. Using absolute URLs is recommended.