12-20-03 Browser as Desktop UI
I began giving seminars before I knew much about web technology. I knew
enough to put up an HTML form that could be printed and faxed, and this seemed to work.
People were comfortable faxing credit-card numbers, but secure servers still
required pricey proprietary technology (it was only a few years ago that public
domain versions of these became legal, because of patent expirations). Web
programming meant CGI, and I didn't understand that at the time (even when I did
tackle it, my first programs were in C++, and a compiled language is not
conducive to web-style rapid development). It was a solution that worked, and
any solution that works tends to persist far longer than you might plan or
desire (there's a maxim in there somewhere).
As a result we've ended up with piles of paper information. Your best
customers are the ones that have already bought from you, and I have been
looking for better ways to get the word out when I give a seminar. One obvious
way to do this is get all these names and addresses entered in so that we can
send out postcards and messages announcing events.
The question is: what technology to use? My Dad will be doing the data entry;
he works on Windows and is reasonably comfortable with any program that has a
straightforward user interface. So I could use FileMaker Pro (something I've had a little experience
with in the past, but I don't remember much about it now).
More important is ease/rapidity of development, as well as control the
ability to do anything that I want. For example, I think I'll want to
automatically zip files and upload them to my server, which Dad doesn't know how
to do. But it's possible I may want to do something that I can't imagine right
now and that may be out of the purview of FileMaker (or possible, but requiring
writing an external DLL and linking to it, etc.). I want something I can get
working fast, but that doesn't limit me or require a herculean effort to add any
feature I want.
Naturally, my first choice for this is Python, but then I have the problem of
deciding what GUI to use. Python has GUIs; too many of them, in fact (PythonCard
is very promising), and none that are second nature to me , so I don't have a
sense of how long that would take. Besides, a full-blown GUI is overkill. All I
need is simple forms and buttons. Like HTML. Yes, that would be ideal, if I
could just lay out forms in HTML and use a web browser as the GUI.
I know that Python has the webbrowser module that starts up an
instance of the browser of choice on the user's machine, opening the URL of your
choice. I also know that the SimpleHTTPServer module allows you to create
a little web server. All I need to do is start up both and get them talking to
each other.
The default location of the SimpleHTTPServer is 127.0.0.1:8000. To
make the connection, I start the browser at that
URL, then start the SimpleHTTPServer, which delivers the forms
containing the database information, and handles requests (although
SimpleHTTPServer handles requests only in the form of GETs, other Python
server libraries are more sophisticated but also more complex, and I'm
just trying to get away with "the simplest thing that could possibly work"
here).
I had to poke around a little to see where the GET information ended up
inside the send_head() method that's overridden in the class derived from
SimpleHTTPServer. It's encoded in the path variable, but this
information is easily extracted using the Python cgi module. You end up
with a dictionary of key-value pairs taken directly from the HTML form.
The buttons in the HTML form all have NAME="Command" and VALUE
tags that indicate the button's function. The VALUE appears in the CGI
dictionary object associated with the key "Command", so all you have to do is
see what the command is and act on that.
I started out with the dumbdbm database, but then discovered (through
the act of programming) that it would be easier just to use shelve and
store objects. Both approaches look like a dictionary, where the record key is
the dictionary key. I keep track of the next available key within the database
itself, using the special record 'nextKey' (shelve allows you to mix
record types with impunity). Each record has an initial field which is a boolean
indicating whether the record is valid or not. In order to delete records it
seemed simpler to turn off this flag, rather than using the del keyword
and actually removing the entry (because then you have the problem of traversing
past the 'empty spot', since its index is taken up. I would have used an
iterator, but Python's iterators only move one way). The only drawback to this
approach is that record zero has become a special case, but the solution to that
may become obvious as time passes, or it may also become clear that it's unimportant.
Shutting down the program is an interesting challenge. First, there's no
"stop" command that I know of for the server. If you use sys.exit() the
exception will be caught by the server (who's job is, after all, to keep running
no matter what). os._exit(0) exits "cleanly" (without producing error
messages), but if you call it within the send_head() function then the
browser gives a disturbing message. I solved the problem using a Timer,
with the following line:
threading.Timer(1, lambda: os._exit(0)).start()
send_head() completes and returns a page telling the user the program
has shut down, then the timer shuts down the server. But before this happens,
the program performs a couple of different backups: a conversion to CSV (comma-
separated values) format using the csv module, and a zip of the data
files using the zipfile module. I will probably add a separate button
that will upload the files to an off-site repository using ftp. Notice that the
vastness of the Python standard library toolset is one of the reasons you can
create complex programs so quickly.
Here's the final
program, which took me a couple of days to write (which was primarily spent
figuring out the issues; obviously you can adapt what I have here to your own
purposes in a few minutes).
The program initializes the database with dummy data to give you something to
look at. You only need Python installed to
run it, and all you have to do is either double-click on the program file, or run
python FlatFiler.py
This now provides a simple GUI suitable for many different kinds of
applications (and the simplicity of the GUI is limited only by your knowledge of
HTML).
Note that everything is in a single program file. A benefit to this approach
is the ease of upgrades, and that's something I anticipate doing. This way, I
can email a single Python file to Dad and teach him to drag and drop it out of
the email on top of the old file, click "yes" to overwrite, and then we're
upgraded.
Of course, you could easily adapt this system to using a regular DBMS like
MySQL, since there are Python adapters for MySQL, and MySQL runs as a
standalone program on a desktop. Or you could just as easily use this system as
an interface to a non-database program.
The initial version of the program combined model and controller, which I
knew was poor practice but it seemed reasonable because there was so little code
necessary to control the model why not just code it directly into the
controller code? (The same thinking is the slippery slope to many Visual-Basic
style messes.) I discovered, however, that a new SimpleHTTPRequestHandler
is instantiated every single time that a request comes in, so there wasn't any
way to store persistent values there, so in the end I was forced to separate the
model and the controller. However, this cleaned up the code, making the
implementation simpler and much easier to debug. In addition, changing the
implementation of the database becomes easier because it is nicely hidden within
the DataBase class.