ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Piddle Graphics Online

by Michal Wallace
06/28/2001

In Generating Graphics With Piddle we generated a gantt chart using piddle and the Python Imaging Library. Now we'll extend this concept to generate graphics in real time on the Web. We'll build on the code from the previous article, making references to it as we go along.

Our original script contained a hardcoded data structure that controlled what tasks were displayed, so changing the image required changing the code. That approach works fine for creating images one at a time, but if we want our drawings to respond to input from our users, we'll have to separate the code and data.

Python offers dozens of data storage options as well as a huge variety of web modules, but to keep things simple, we'll stick to the basics: python's bundled shelve and cgi modules. With these tools in hand, we're ready to build an HTML interface for customizing our chart.

The Interface

We'll use a model-view-controller design pattern for our program. The model is the data stored in a shelf file. The view will show the rendered gantt chart and lists individual tasks. Each task will have a link to the controller page, to let us add, edit, and delete tasks. The following diagram illustrates this design:

Data flow diagram.

This translates to four files:

(Because space prohibits us from discussing every line of code, we have packaged the full source in a zip file.)

So What's a Shelf?

A shelf is basically a python dictionary stored on disk. The shelve module uses pickle to convert objects to and from strings, and stores those strings in Unix-style database files via anydbm. But we don't have to worry about all that. All we do is tell shelve where to store the data.

We'll prepopulate our shelf with some variables using the same data structures from last time. To set it up, we'll need some throwaway code. You can run the following as a script or just type it into the python interpreter interactively:

import shelve

# open the shelf (and create it if it's not there):
s = shelve.open("model.shelf")

s["now"]=3
s["titles"]=["apr","may","jun","jul","aug","sep"]
s["tasks"]=[{"label":"a task", "boxes":[( 3, 10, "crimson"),
                                        (12, 13, "teal")]}]

# save the data to disk and close the shelf
s.close()

You can verify that this worked by opening the shelf interactively and examining its contents:

>>> import shelve
>>> s = shelve.open("model.shelf")
>>> s.keys()
['now', 'tasks', 'titles']
>>> s["now"]
3

We can now start coding a CGI script to use our data model.

CGI

The CGI protocol defines how a web server and script communicate. The server defines some environment variables and makes data from the browser available to the script. The script responds with the type of the content it generates, the content itself, and any other information the browser needs.

Python's cgi module takes care of the server side, but we have to create a well-formed response ourselves. A simple example looks like this:

#!/path/to/python
print "content-type: text/html"
print
print "hello, world!"

Note that every python CGI script must print at least a "content-type" header followed by a blank line, but not every script needs the cgi module. We'll use cgi later, to examine form submissions on the controller page.

To run the script above, you'll need to configure your web server to handle CGI. On Unix-likesystems, this often only requires setting the read and execute bits on your script. On some systems, CGI may work only in certain directories (eg, cgi-bin). Consult your server's documentation for specifics.

Putting it Together: the View Page

With what we've learned and a little bit of HTML, view.cgi is easy:

The script is mostly plain HTML or code we've already seen, except for the last item, displaying a link to edit each task. This requires a loop:

# display the tasks as links:
for row in range(len(tasks)):
  print '<li><a href="controller.cgi?action=edit&row=%i">%s</a>' \
     % (row, tasks[row]["label"])
  print ' [<a href="controller.cgi?action=delete&row=%i">delete<a>]</li>' \
     % row

Make sure that the web server has rights to read and write model.shelf, and load view.cgi in your browser. If you run this cgi script now, you will see a broken image and the list of tasks. Next we will work on the chart.

Displaying the Image

To create the image, we'll use the original gannt chart code, with three major changes. The new version will

The first two changes are simple; the third is trickier. In theory, we'd just save the piddle canvas to sys.stdout, but there are two problems with this approach.

The first problem is that piddlePIL doesn't allow saving images to file objects. Since it comes with full source, however, we can fix it ourselves. Search in piddlePIL.py for the save method. You'll see the problem. A couple lines down, there's a bit of code that says:

if hasattr(file, 'write'):
    raise 'fileobj not implemented for piddlePIL'

Replace that with the following:

if hasattr(file, 'write'):
    self._image.save(file, format)
    return

Now, displaying the image on the web is easy. Here's the code, from the bottom part of chart.cgi:

print "content-type: image/jpeg"
print
import sys
c.save(file=sys.stdout, format="jpeg")

The second problem only happens under Windows: the output gets corrupted because stdout is not in binary mode by default. The fix is arcane, but concise:

import os, sys
if sys.platform=="win32":
    import msvcrt
    msvcrt.setmode(sys.__stdin__.fileno(), os.O_BINARY)
    msvcrt.setmode(sys.__stdout__.fileno(), os.O_BINARY)

Place this before the content-type line and chart.cgi should have no problem displaying the image.

Last Piece: the Controller

The controller is in charge of managing our data. Essentially, it does four things:

By default, controller.cgi shows the "add" form. A parameter called action tells it to do something else. We can pass action, either as part of a query string (data following the question mark in a URL) or via a form submission. The cgi module can handle either method through the FieldStorage class. FieldStorage can be treated almost like a dictionary, although it doesn't implement every dictionary method. For simple cases like this, it returns values as cgi.MiniFieldStorage objects. The following code shows FieldStorage in action:

import cgi
request = cgi.FieldStorage()

action = "add" # by default
if request.has_key("action"):
    action = request["action"].value

In controller.cgi, a set of if/elif blocks looks at the action parameter and calls the appropriate function. In a sense, the controller is several CGI scripts rolled into one. We could have broken these into separate files, but I prefer to keep related logic together.

Some of the available actions don't return a page to the browser but, instead, redirect it to another page. In our case, saveTask and deleteTask both call backToView, which returns a Location header rather than a content type. The following line sends the browser back to the view page:

print "Location: view.cgi"

The rest of controller.cgi, including the code to save and delete tasks, is pretty straightforward. Consult the source for details.

The End

That's it for this whirlwind tour of the gantt chart CGI application. To recap, we've seen how to store and retrieve data from a python shelf, communicate with the browser through CGI, and use piddlePIL to generate graphics in real time on the Web.

Copyright © 2009 O'Reilly Media, Inc.