Aquarium Tutorial

Aquarium is a Web application framework, written in Python. A Web application is an application that users interact with, using their Web browser; it executes business logic on the server the user is interacting with. Some examples of Web applications are ecommerce systems and GMail. A framework is a set of super classes, APIs, and other libraries appropriate for solving a particular domain of problems--in this case, developing a Web application.

This tutorial assumes you know Python and HTML. Some previous experience developing Web applications is helpful. I'll assume that you have Python installed and that you know how to install additional Python packages. To fully understand Aquarium, you must also be very comfortable with object oriented programming.

1. Installing Necessary Software

You must download and install Cheetah. Next, download and install Aquarium. Installing Aquarium will also install Glass, a simple, yet flexible Web server based on the Python standard libraries. This tutorial is based on Glass since it's easy to use and comes with Aquarium. Both Cheetah and Aquarium are installed using the standard python setup.py install.

2. Serve a Static File

Let's start by serving a static file. A static file is a file that does not change. Serving static files is something that any Web server can do.

This is the file structure so far:

./htdocs
./htdocs/favicon.ico
./htdocs/index.html
./start.py

The name htdocs will be familiar to those who have used the Apache Web server; it is the directory that will contain the static content to be served by the Web server. favicon.ico is just a simple favicon file that most Web browsers like to request automatically. index.html is just a straightforward Web page:

<html>
<head>
  <title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>

start.py is the script that launches Glass:

#!/usr/bin/env python

from glass.HTTPServer import test


try:
    print "Use Control-C to exit.  In Windows, use Control-Break."
    test()
except KeyboardInterrupt:
    pass

Notice that this is a very simple launch script that imports a function and executes it. A small amount of code has been added so that you can stop the Web server using Control-C. In Windows, use Control-Break.

You can start the Web server via:

./start.py

Once the Web server is started, you can connect to it via http://localhost:8000. If you already have something running on port 8000, pass the port you'd like to use as the first argument to start.py.

3. Serve a Template

Cheetah is a Python-powered template engine. It is a way of embedding some business logic in a file that otherwise contains static content, such as HTML. There are many styles of templating engines. The different styles are somewhat a matter of taste. Cheetah is based on the project Velocity, which may be familiar to some Java programmers. If you know Python, learning Cheetah is trivial. Here is a sample:

<p>Let's count to $count:</p>

#for $i in $count
  $i<br>
#end if

As you can see:

You can learn more about Cheetah by reading the users' guide.

Getting back to the task at hand, here's the file structure so far:

./htdocs
./htdocs/favicon.ico
./site-packages
./site-packages/conf
./site-packages/conf/AquariumProperties.py
./site-packages/screen
./site-packages/screen/index.tmpl
./start.py

Two files have been added, site-packages/conf/AquariumProperties.py and site-packages/screen/index.tmpl. Everything else has stayed the same. The directory site-packages will contain Aquarium-specific code. You can change the name of that directory, but if you use the default name site-packages, you won't need to configure anything. Similarly, Glass knows the default directory for static content is htdocs, and if you want to use a different name, you have to configure Glass to tell it where to look.

The file site-packages/conf/AquariumProperties.py is the configuration file for Aquarium. Feel free to steal the one below as the basis for your own projects. It is heavily documented, so feel free to read it later and experiment with changing various settings. You can skim it now, but you don't have to. In the future, I expect that more behavior will be configured to work by default. Hopefully, one day, no configuration file will even be necessary if you are comfortable with the defaults, but in the meantime, you can use the one below:

"""This is the "configuration file" for the program.

The AquariumProperties module serves as the "configuration file" for
the program.  Users of this program should edit this file to suit their
needs.

"""

# What urlscheme module should be used.  urlscheme modules dictate how 
# the screen parameter is positioned in URL's.

URL_SCHEME = "ScriptName"

# Will cookies be used on this site?  If this variable is set to 1, 
# we'll automatically load the cookie object (which acts like a 
# dict) into ctx.cookie.  

USE_COOKIES = 1

# Will sessions be used?  Session information will be stored in
# ctx.session.  The session identifier, sid, is accessable via
# ctx.session["sid"].  It will be persisted via ctx.url.screen and/or 
# via cookies.

USE_SESSIONS = 1
if USE_SESSIONS:

    # If so, should we *try* to use cookies for persisting the 
    # session identifier?

    USE_SESSION_COOKIES = 1
    if USE_SESSION_COOKIES:

        # Should we force the use of cookies for sessions (i.e. never
        # transmit the sid via GET or POST variables)?

        FORCE_SESSION_COOKIES = 1

        # Set the cookie to expire based on MAXIMUM_SESSION_LIFETIME?  
        # If 0, the default, let the cookie continue exactly until the 
        # user closes his browser.

        SET_COOKIE_EXPIRATION = 0

    # Which session container module should we use?  Beware that not all
    # session containers are appropriate for all environments.

    SESSION_CONTAINER_MODULE = "SessionContainer"

    # What is the maximum session lifetime in seconds?

    MAXIMUM_SESSION_LIFETIME = 60 * 60 * 2      # I.e. 2 hours

# Will a database be used?

USE_DATABASE = 0
if USE_DATABASE:

    # Which Aquarium database assistant module should we use?  You'll 
    # probably want to just stick with the default, DatabaseAssistant.
    # Note, if you decide to use QuickSwitchDatabaseAssistant instead, 
    # (so that you can rapidly toggle between different RDBM's), you'll
    # probably want to hack the python database module argument as well
    # as the connection arguments so that they vary based on your 
    # choice of DBM's (just move them around within the if statements).

    AQUARIUM_DATABASE_ASSISTANT_MODULE = "QuickSwitchDatabaseAssistant"
    if (AQUARIUM_DATABASE_ASSISTANT_MODULE ==
        "QuickSwitchDatabaseAssistant"):

        # Which RDBM do you wish to use (more information about how
        # to set this up can be found in 
        # database/QuickSwitchDatabaseAssistant).

        DATABASE_RDBM = "mysql"
        if DATABASE_RDBM=="mysql":

            # Which Python database module should we use?  You'll 
            # have to install this module on your system yourself.
            # For more information, check here: 
            # <http://www.python.org/topics/database/ ...
            # ... DatabaseAPI-2.0.html>.

            PYTHON_DATABASE_MODULE = "MySQLdb"

            # Specify the database connection parameters here.  
            # The number and name of the arguments in this list 
            # and dict may vary based on the python database module
            # that you selected above.  Remember, if you change 
            # these and you're using persistent database 
            # connections (not programmed yet), you'll probably 
            # have to restart your Web server before your changes 
            # will take effect.

            DATABASE_CONNECTION_ARGS = ()
            DATABASE_CONNECTION_KWARGS = {
                "host":"localhost",
                "user":"test",
                "passwd":"",
                "db":"test"
            }

        elif DATABASE_RDBM=="sqlserver":
            PYTHON_DATABASE_MODULE = "ODBC.EasySoft"
            DATABASE_CONNECTION_ARGS = ()
            DATABASE_CONNECTION_KWARGS = {
                "dsn":"seamstress_exchange",
                "user":"seamstress_exchange",
                "password":"se125xch"
            }

# Should gettext be used?

USE_GETTEXT = 0
if USE_GETTEXT:

    # What are the args and kwargs to pass to gettext.translation?
    # Aquarium will take care of the "languages" argument. 

    GETTEXT_ARGS = ("seamstress-exchange", "po")
    GETTEXT_KWARGS = {}

# Will any output filters be used?  Since we buffer out content, you can
# use filters on the buffered content before sending it to the client.

USE_FILTERS = 0
if USE_FILTERS:

    # If so, please list the names of the filter modules here.  The 
    # first filter in the list will be used first.

    FILTER_MODULES = []
 
# Should Aquarium automatically clear out modules of certain types on 
# every page load if at least one of those modules is stale?  This 
# makes sense during development with environments such as mod_python, 
# but not in production or in environments such as CGI (actually, it's 
# harmless for CGI).  The value may be either a boolean or a list of 
# module types to automatically clear.  
#
#     True -> ["aquarium.layout","aquarium.navigation",
#              "aquarium.screen", "aquarium.widget"]
#     False -> []
#
# CAUTION: modules of these types must *not* maintain any state, as it 
# will be lost on every page load.

CLEAR_MODULES = True

# Should Aquarium compile Cheetah templates automatically?  If you 
# decide to precompile templates, make sure Cheetah doesn't create a 
# bunch of empty __init__.py files that override the __init__.py files 
# used by Aquarium.  That will break packagePath and result in 
# ImportErrors.

CHEETAH_COMPILE = 1
if CHEETAH_COMPILE:

    # If so, what is the name of the directory is should save the 
    # compiled templates to?  If this is set to None, templates will 
    # be compiled in the same directory they are found.  Remember that 
    # this directory must be writeable by the Web server process.  

    CHEETAH_COMPILE_DIRECTORY = "cheetah-cache"

# Output headers to disable browser caches?

USE_ANTI_CACHING_HEADERS = True

# You may define your default DTD here.

DEFAULT_DTD = (
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' +
    '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
)

# These are the defaults for the HTML head.

DEFAULT_TITLE = "Hello World!"
DEFAULT_META_TAGS = ""          # You may add more if you wish.

# These are the default attributes for the body tag (remember to keep in
# mind which attributes are allowed by the DTD defined above). 

DEFAULT_BODY_ATTRIBUTES = {}    # You may add more if you wish.

# When the user first comes to the site, this is the screen he'll be
# transferred to.  This is the module name (i.e. use dots, not 
# slashes) of the screen relative to the screen package.

DEFAULT_SCREEN = "index"

# On the exception screen, we'll output the email address of the Web 
# master.  You may define that here if you are disatisfied with the 
# default value.

WEBMASTER_EMAIL = "jjinux@users.sourceforge.net"

Now, let's look at the template, site-packages/screen/index.tmpl:

#extends aquarium.layout.HTML
#implements __call__


<html>
  <head>
    <title>Hello, World!</title>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>

Other than the two lines at the top, it looks like normal HTML!

Let's look at the first line:

#extends aquarium.layout.HTML

Every Cheetah template is implicitly a class. In Aquarium, every Cheetah template has an explicit superclass. The class aquarium.layout.HTML knows about things such as Content-type: text/html, controlling the browser cache, etc. Choosing the right superclass is the first thing you must think of when writing a templating.

The second line is:

#implements __call__

I said that every Cheetah template is implicitly a class. You can add methods to that class via code such as:

#def foo(a, b)
Hi!
#end def

(Note carefully that there is no : at the end of that first line!)

The line #implements __call__ says that everything that isn't explicitly in a method is implicitly in a method named __call__. That means that all of the HTML in the above example is implicitly in a method named __call__.

You'll see a lot of methods named __call__ in Aquarium because that is the "main method" in most Aquarium classes.

4. Use a Layout

I said that every template has an explicit superclass. In the previous example, site-packages/screen/index.tmpl subclassed aquarium.layout.HTML. Notice that layouts are the parent classes of screen templates. If there are many screens, the common look and feel can be factored into a layout module. By choosing a different layout module, the screen can have a completely different look and feel. (Advanced object oriented developers can make use of delegation to choose the look and feel dynamically, but that is outside of the scope of this tutorial.)

Here's the file structure:

./htdocs
./htdocs/favicon.ico
./htdocs/images
./htdocs/images/aquarium.png
./site-packages
./site-packages/conf
./site-packages/conf/AquariumProperties.py
./site-packages/layout
./site-packages/layout/Vertical.tmpl
./site-packages/navigation
./site-packages/navigation/TopNavigation.tmpl
./site-packages/screen
./site-packages/screen/index.tmpl
./start.py

A new directory htdocs/images with a file named aquarium.png has been added. That's straightforward. Two new templates have been added site-packages/layout/Vertical.tmpl and G``site-packages/navigation/TopNavigation.tmpl``. site-packages/screen/index.tmpl has been updated as well.

Let's look at site-packages/screen/index.tmpl:

#extends aquarium.layout.Vertical
#implements __call__
#def getTitle: Howdy!


<h1>$getTitle</h1>

It's much shorter than before! Apparently, the superclass must be taking care of more. Notice that the superclass is now aquarium.layout.Vertical, which matches one of the new files, site-packages/layout/Vertical.tmpl. There's also a new line:

#def getTitle: Howdy!

Notice the :. This is a Cheetah one-liner. This line is equivalent to:

#def getTitle()
Howdy!
#end def

We also call this method below:

<h1>$getTitle</h1>

Notice that Cheetah lets you leave out the parenthesis if there are no arguments.

If we start the server and run the page, the HTML generated is:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html>
<head>
  <title>Howdy!</title>

    
</head>
<body><img src="http://localhost:8000/images/aquarium.png">

<table width="600" align="center"><tr><td>
  

<h1>Howdy!</h1>

</td></tr></table>
</body>
</html>

Obviously, the superclass is doing a lot more!

Now let's look at that superclass, site-packages/layout/Vertical.tmpl:

#extends aquarium.layout.CssAndJavaScript


#def __call__(callNext, *args, **kargs)
    #set $callNextStr = callNext(*args, **kargs)
$iLib.call("navigation.TopNavigation")
<table width="600" align="center"><tr><td>
  $callNextStr
</td></tr></table>
#end def

Wow, there's a lot going on! It looks more like code than HTML! Aquarium makes heavy use of object oriented programming and applies the philosophy of "don't repeat yourself" to HTML as well. Let's look at it one piece at a time.

The first line is:

#extends aquarium.layout.CssAndJavaScript

This layout itself inherits from another layout. CssAndJavaScript is a layout that comes with Aquarium. It knows about the general structure of an HTML page. That's why you don't see any <html> and <body> tags. That's one thing that you won't need to repeat in every screen. (Note, Aquarium isn't constrained to HTML. By subclassing a different layout, you can even output binary, such as for dynamic images.)

Instead of #implements __call__, everything is wrapped in:

#def __call__(callNext, *args, **kargs)
#end def

That's because this __call__ method accepts arguments, (callNext, *args, **kargs). Now here's where it gets interesting. In many Web systems, there is the idea of a "server-side include", and Aquarium is no different:

$iLib.call("navigation.TopNavigation")

That line is a server-side include of the file site-packages/navigation/TopNavigation.tmpl.

Some Web systems also have the concept of templates inheriting from superclasses. Hence, I could have added the line:

<h1>$getTitle</h1>

This would have been an instance of the superclass calling a method defined in the subclass. Most people will find this concept familiar.

However, Aquarium goes further and borrows a concept from the Mason project in Perl. The __call__ method of a layout "wraps" the __call__ method of the screen. Hence, the layout chooses when the screen should run by calling the method callNext:

    #set $callNextStr = callNext(*args, **kargs)
...
<table width="600" align="center"><tr><td>
  $callNextStr
</td></tr></table>

This even works for layouts inheriting from other layouts! Each level in the inheritance hierarchy wraps the level below it. That means the top level in the inheritance hierarchy gets to run first, which is the exact opposite of the normal notion of extending a superclass's method!

If the above code with $callNextStr and (*args, **kargs) seems complicated, don't worry. It is a very carefully thought out idiom. Most of the time, you can just follow the idiom. The important thing is that you can wrap the $callNextStr, which will contain the content from the screen itself, with any other code you want. Later, we'll see that this idea of inversely extending a method can be used in Python code as well.

Before I finish, let's take a quick look at site-packages/navigation/TopNavigation.tmpl, the file that was treated as a server-side include:

#extends aquarium.util.AquariumTemplate
#implements __call__


<img src="$url.img("aquarium.png")">

Hopefully, things are beginning to look familiar. This template subclasses aquarium.util.AquariumTemplate. Most navigation modules do. AquariumTemplate does very little, except provide the glue between Cheetah and Aquarium. It's the most basic superclass for templates in Aquarium.

Notice the line:

<img src="$url.img("aquarium.png")">

This is a simple <img> tag, but the URL is generated dynamically. The code $url.img("aquarium.png") will generate a full path to the file htdocs/images/aquarium.png. Generating all URLs dynamically is an important part of maintaining flexibility should you need to change Web servers.

This illustrates an important design consideration in Aquarium. As much as possible, Aquarium aims to protect your code (or at least help you to protect yourself!) from things that are likely to change, such as the choice of RDBMS, Web server, or even how URLs function in your Web application.

5. Controllers and Views

So far, the application hasn't really done anything. A Web application isn't very interesting if it doesn't interact with the user. Let's create a form. I'll use a trivial calculator as an example.

Aquarium is a model view controller, MVC, framework. Actually, you might say it's a view controller framework, because it doesn't dictate how your model should work. Projects have wildly different needs when it comes to models, so it is pointless for Aquarium to dictate how the model must work. Some projects may want straight forward access to an RDBMS, and Aquarium provides a convenient layer for this. Other projects may want to use an object-relational mapper such as SQLObject, which works well with Aquarium. Some projects may not even use an RDBMS at all, but may instead rely on a home grown RPC mechanism to talk to a home grown data source, and this works as well. Needs vary, so Aquarium doesn't dictate the "one true way".

Here's the file structure:

./htdocs
./htdocs/favicon.ico
./htdocs/images
./htdocs/images/aquarium.png
./site-packages
./site-packages/conf
./site-packages/conf/AquariumProperties.py
./site-packages/layout
./site-packages/layout/Vertical.tmpl
./site-packages/navigation
./site-packages/navigation/TopNavigation.tmpl
./site-packages/screen
./site-packages/screen/index.py
./site-packages/screen/index_view.tmpl
./start.py

Not much has changed. site-packages/screen/index.tmpl has been replaced by site-packages/screen/index.py and site-packages/screen/index_view.tmpl. The file index.py is the controller, and it is written in Python. index_view.tmpl is the view, and it is written in Cheetah. They are both screens. They both have a __call__ method, and they both make use of that strange "inverse extension" feature we saw earlier. They live side by side because experience has shown that if a developer wants to look at one, he'll likely want to look at the other. (This is even more important when you have a rich file structure to better organize a huge number of screens such as:

site-packages/screen/a/b/c/d/index.py
site-packages/screen/a/b/c/d/index_view.py

Let's look at the view, site-packages/screen/index_view.tmpl, first:

#extends aquarium.layout.Vertical
#def getTitle: Howdy!


#def __call__(result=0)
<h1>$getTitle</h1>

<form action="$url.screen("index")">
  <table>
    <tr>
      <td>Left:</td>
      <td><input type="text" name="left"></td>
    </tr>
    <tr><td colspan="2">+</td></tr>
    <tr>
      <td>Right:</td>
      <td><input type="text" name="right"></td>
    </tr>
  </table>
  <input type="submit" value="=" name="action:Calculate">
  <h3>$htmlent($result)</h3>
</form>
#end def

The #implements __call__ has been replaced with:

#def __call__(result=0)
#end def

That is because the __call__ method now accepts an optional argument, result=0. (Note that this argument is passed by the controller. It is not implicit access to form data which is done in some Web systems.) The action of the form is $url.screen("index"). Once again, this is a dynamically generated URL used to maintain flexibility. In this case, it'll translate to the /index screen, i.e. the controller.

The rest of the form looks pretty standard. A form macro utility called FormUtil will be shown later, and that will reduce the amount of "code". One line looks strange:

<input type="submit" value="=" name="action:Calculate">

action:Calculate tells the controller to execute the Calculate action. This is a trick borrowed from Zope. You can also pass the desired action using a GET variable or even a hidden form variable. Attaching it to the submit button lets you use a different action for each button, but there are many ways to pass the desired action.

Finally, we see:

<h3>$htmlent($result)</h3>

The value of result is output, but it is HTML escaped. Properly escaping data is something that any Web developer needs to at least keep in mind. When using FormUtil, Aquarium can do it for you. There are also ways to make Cheetah do it automatically. This may become more the norm in the future. In this case, I happen to know that $result is an int, so it's completely unnecessary; however, I put it there to get you to remember to think about HTML escaping things properly.

Before I show the controller, let's review how a traditional Web application works:

  1. The user clicks on a link or button.
  2. The application completes any actions that need to be done.
  3. The application prepares data to show to the user.
  4. The application generates HTML (etc.) output.
  5. The user reads and interacts with the output.
  6. Repeat.

In Aquarium, this translates into:

  1. The user clicks on a link or button.
  2. The controller completes any actions that need to be done.
  3. The controller prepares data to show to the user.
  4. The controller "forwards" control to the view, passing necessary data.
  5. The view generates HTML (etc.) output.
  6. The user reads and interacts with the output.
  7. Repeat.

(Naturally, AJAX mixes things up a bit, but if you understand traditional Web development in Aquarium, you'll be able to apply AJAX to Aquarium.)

Keep this flow in mind when reviewing the controller. Let's look at the controller now, site-packages/screen/index.py:

from aquarium.screen.Controller import Controller 


class index(Controller):

    def __call__(self):
        ctx = self._ctx
        self.executeAction()
        ctx.iLib.forward("index_view")

    def doCalculateAction(self):
        ctx = self._ctx
        try:
            result = int(ctx.form["left"]) + int(ctx.form["right"])
        except (ValueError, KeyError):
            ctx.actionResults = "Ah, nuts!"
            return
        ctx.iLib.forward("index_view", result)

Let's review each part:

from aquarium.screen.Controller import Controller 


class index(Controller):

As usual, the first thing to think about when writing a new class is to pick the superclass. In this case, Controller is the basic superclass for controllers. It comes with Aquarium. In most applications, you'll want to subclass Controller and create your own application-specific superclass.

Note that the name of the class matches the name of the module. This is a common idiom for Java programmers, and it is the norm in Aquarium. Also notice the main method is __call__(self) which should be familiar by now. Note that in Cheetah, you don't have to explicitly accept self as the first argument, but, naturally, you do in Python.

The next line is:

ctx = self._ctx

This sets up an alias to self._ctx. In Aquarium you gain access to many things through the Context object, and almost everything has a reference to the Context object, usually named self._ctx. For example, form data can be accessed via ctx.form and session data can be accessed via ctx.session. In Cheetah the Context object is placed into the searchList so that you can access anything in the Context object directly. For instance, you can use $form and $session. Hence:

In Python             In Cheetah
---------             ----------
self._ctx.foo    =    $foo

Here is a reference for what you'll find in the Context object.

The next line is:

self.executeAction()

This means if there is an action that the user is executing, execute it now. You'll remember in the form, there was a line:

<input type="submit" value="=" name="action:Calculate">

Hence, calling self.executeAction() implicitly calls doCalculateAction in the controller when the user submits the form. Note the naming convention. Because you call executeAction explicitly, you have the opportunity to do some initialization first if you need to.

The last line is:

ctx.iLib.forward("index_view")

This is a JSP-style forward to the view. That is, control is transferred to the view. Note that ctx.iLib.forward does not return. (Internally, it's implemented as an exception.) The view's __call__ method is called with any additional arguments passed to ctx.iLib.forward.

Let's look at the action now:

def doCalculateAction(self):
    ctx = self._ctx
    try:
        result = int(ctx.form["left"]) + int(ctx.form["right"])
    except (ValueError, KeyError):
        ctx.actionResults = "Ah, nuts!"
        return
    ctx.iLib.forward("index_view", result)

As I said before, the naming convention is doFooAction. Setting an alias to the context object should be familiar by now. ctx.form["left"] is the value of the form field left. I mentioned ctx.form earlier as well. I'm using a try/except block to catch all exceptions; I'll describe FormValid, a form validation library, later. Here, we add up the value of the two form fields, and if nothing goes wrong, we once again do a forward to index_view. In this case, we pass result as an argument. If something does go wrong, we set ctx.actionResults to an error message and return. Control will thus return from self.executeAction in the __call__ method.

What's the point of ctx.actionResults? It's used in site-packages/navigation/TopNavigation.tmpl which now reads:

#extends aquarium.util.AquariumTemplate
#implements __call__


<img src="$url.img("aquarium.png")">

    #if hasattr(self._ctx, "actionResults")
<p>$actionResults</p>
    #end if

That is, it's just a place to put an error message so that it can be output later. You may instead wish to put it in the session if you need to do a redirect to another page.

To summarize, the steps shown above translate into:

  1. The user clicks on a link or button.
  2. The controller completes any actions that need to be done by calling self.executeAction().
  3. The controller prepares data to show to the user.
  4. The controller "forwards" control to the view, passing necessary data. This can be done from either __call__ or one of the doFooActions (or any other method they call, naturally). The forward is done via calling ctx.iLib.forward(screenName, arguments...).
  5. The view generates HTML (etc.) output.
  6. The user reads and interacts with the output.
  7. Repeat.

6. Use FormUtil, FormValid, and Sessions

I've mentioned FormUtil, a form macro utility; FormValid, a form validation library; and sessions. Let's see them in action.

Here's the file structure:

./htdocs
./htdocs/favicon.ico
./htdocs/images
./htdocs/images/aquarium.png
./site-packages
./site-packages/conf
./site-packages/conf/AquariumProperties.py
./site-packages/layout
./site-packages/layout/Vertical.tmpl
./site-packages/navigation
./site-packages/navigation/TopNavigation.tmpl
./site-packages/screen
./site-packages/screen/index.py
./site-packages/screen/index_view.tmpl
./start.py

Only the two screens have changed.

Let me start by saying modern HTML forms are complex and tedious! Getting all of the intricate functionality to work just right in the name of usability takes a lot of work. The more forms you have, the more work. Form generation libraries and form validation libraries have existed for a long time. However, often people shy away from them because they are afraid that they won't meet their needs. Hence, they're stuck writing something completely custom, which again takes a lot of work. The best of both worlds would be a library that does as much as of the tedious, "normal" work as possible, but gets out of the way quickly when you need to "get custom". For instance, FormUtil is "layered" so that you can use the top-level functions to do everything, or you can occassionally make use of the low-level functions from within your highly custom form. Furthermore, you can use the HasFriends mixin to add your own top-level functions, but I'll let the reference documentation cover that.

Both FormUtil and FormValid have their own reference documentation, like the rest of Aquarium. Instead of duplicating it here, I'll just give a flavor of the two libraries within our sample app.

Let's start by looking at the view and its usage of FormUtil:

#extends aquarium.layout.Vertical
#def getTitle: Howdy!


#def __call__(result=0)
<h1>$getTitle</h1>

<form action="$url.screen("index")">
    #set formUtil = $iLib.aquariumFactory("widget.FormUtil", 
                                          defaults=[$form])
  <table>
    $formUtil.field("Left:", "left", "text")
    <tr><td colspan="2">+</td></tr>
    $formUtil.field("Right:", "right", "text")
  </table>
  <input type="submit" value="=" name="action:Calculate">
  <h3>$htmlent($result)</h3>
</form>
#end def

The code has gotten a little smaller, but at the same time it's increased in functionality. If you trying running the application, the form fields now provide defaults if your form submission fails. There's also an error message next to the form field if that form field is invalid. Naturally, some design love is in order (a la CSS), but this is definitely an improvement!

The first line that is new is:

#set formUtil = $iLib.aquariumFactory("widget.FormUtil", 
                                      defaults=[$form])

iLib.aquariumFactory(moduleName, args...) is used all over the place in Aquarium. It compiles the Cheetah template (if it is one), imports the module, instantiates the class, passes it a copy of the Context object along with the additional arguments you pass, and then returns the instance--whew! Suffice it to say that dynamic imports are a core part of Aquarium.

What's interesting is the keyword argument that was passed, defaults=[$form]. The defaults keyword argument accepts a list of dictionaries. When FormUtil goes to lookup a default for a field, it looks through each dictionary. If the dictionary defines that key, the value is used for the field default. Hence, the defaults for the whole form are set in one place. In this case, if the user submitted the form, and an error occurred, the form is reshown with the data the user filled in the first time he submitted the form. Naturally, you can use whatever source of data you want for the form defaults.

The next two lines of interest are:

$formUtil.field("Left:", "left", "text")
...
$formUtil.field("Right:", "right", "text")

$formUtil.field is a "high-level macro" that creates a table row with a label on the left, a place for error messages, and a field on the right. In the case of the first line, there's a label named "Left:" and a field of type "text" named "left". It'd be nice if all form fields were so easy and so standard. When things aren't so easy and standard, instead of using $formUtil.field, you might need to fall back to using lower level macros such as $formUtil.textField, $formUtil.label, and $formUtil.getDefault. The important thing is that you don't have to reinvent everything from scratch just because FormUtil doesn't exactly meet your needs!

Now, let's look at the controller and its use of FormValid:

from aquarium.screen.Controller import Controller 
from aquarium.util.ActionResults import ActionResults
from aquarium.util.FormValid import Form, Field


class index(Controller):

    def __call__(self):
        ctx = self._ctx
        self.executeAction()
        ctx.iLib.forward("index_view")

    def doCalculateAction(self):
        ctx = self._ctx
        (values, errors) = self.createCalculateValidator()(ctx.form)
        if errors:
            ctx.session["failures"] = ctx.session.get("failures", 0) + 1
            ctx.actionResults = ActionResults(
                "You have failed %s times." % ctx.session["failures"], 
                errors=errors)
            return
        result = values["left"] + values["right"]
        ctx.iLib.forward("index_view", result)

    def createCalculateValidator(self):
        msg = "You bonehead, use an int!"
        return Form([
            Field("left", msg, cast=int),
            Field("right", msg, cast=int)
        ])

There are some additional imports (Form and Field from FormValid), doCalculateAction has some changes, and there's a new method named createCalculateValidator. Let's look at createCalculateValidator first:

def createCalculateValidator(self):
    msg = "You bonehead, use an int!"
    return Form([
        Field("left", msg, cast=int),
        Field("right", msg, cast=int)
    ])

Instead of saying "Do this, then check this, then do that," FormValid uses a high-level "description" of how the form should be validated. It can do things like check that the field is present, apply a cast, check a regular expression, apply a default value, or even call a user-defined function, which is necessary for true flexibility. It can also understand dependencies among the fields. Last of all, it can even support lists of fields or even rows of columns. For instance, your form might have JavaScript that allows the user to create as many rows as he wants. These can be submitted all at once, and the validator can implicitly loop over each row apply constraints to each column of each row. That is an an advanced topic that I won't cover here.

Looking at the code, createCalculateValidator returns a Form object, which is really a validator. That Form object contains two Field objects. Each Field object has a field name, an error message, and in this case, a cast to int. It is common to isolate the validator in a validator factory method named createFooValidator that matches the doFooAction method. Following this idiom, although not required, increases readability.

Now, let's look at how that validator is used:

(values, errors) = self.createCalculateValidator()(ctx.form)
if errors:
    ...

The validator is created, and then it is immediately called and passed the form. It returns a tuple of two things values and errors. One will be a dictionary, and the other will be None. If the validation succeeds, values will be a "cleaned up" version of ctx.form. All casts will have been applied, defaults will be filled in, and in general, the data should be considered relatively "clean". If validation fails, errors will be a dictionary mapping field names to error messages. If validation fails, if errors will be True, and the next piece of code is:

ctx.session["failures"] = ctx.session.get("failures", 0) + 1
ctx.actionResults = ActionResults(
    "You have failed %s times." % ctx.session["failures"], 
    errors=errors)
return

Here, a value is being added to the session. The session has a dictionary API, no sweat. ctx.actionResults is also set as before. However, this time an ActionResults object is used. ActionResults is really just a container for a string (the main error message) and whatever additional keyword arguments you'd like to pass. In this case, errors is passed. FormUtil knows to look for the errors dictionary in the ActionResults object. This is the only place that FormUtil and FormValid know about each other! They are strongly decoupled by design. You don't have to use one in order to use the other.

If validation succeeded, the code continues:

result = values["left"] + values["right"]
ctx.iLib.forward("index_view", result)

Notice that no cast was needed since it was already done by the validator. Otherwise, this code is like it was before FormValid was applied.

Hopefully, you now have enough of the flavor of FormUtil and FormValid that the reference documentation will be easy to understand.