Author: | Dave Kuhlman |
---|---|
Address: | dkuhlman@rexx.com http://www.rexx.com/~dkuhlman |
Revision: | 1.0a |
Date: | Feb. 2, 2004 |
Copyright: | Copyright (c) 2004 Dave Kuhlman. This documentation is covered by The MIT License: http://www.opensource.org/licenses/mit-license. |
Abstract
This document provides some help with developing user interfaces with the Quixote form2 module. It also describes strategies for improving those user interfaces, converting to other user interface development technologies, etc.
Quixote can be viewed as a simple, easy, quick way to develop front-ends or user interfaces to complex back-end applications and resources.
This strategy has the following advantages:
This document will explain:
Creating a user interface with form2 involves the following steps, which will be described in more detail below:
Here is an example:
zipForm = MyZipForm(name='zip_form', action_url='show_contents')
Explanation:
Here is an example:
zipForm.add(form2.StringWidget, 'file_name', '/home/dkuhlman/tmp.zip', title='File name:', hint='Enter zip file name', size=40, required=False) zipForm.add(form2.SingleSelectWidget, 'sort_order', 'file_name', title='Sort by:', hint='Column to sort by', options=[ ('file_name', 'File name', 'key1'), ('date_time', 'Date/time', 'key2'), ('file_size', 'File size', 'key3'), ('compress_size', 'Compressed size', 'key4'), ], sort=False) zipForm.add(form2.RadiobuttonsWidget, 'columns', 'l', title='Columns:', hint='Columns: s=short, m=medium, l=long, v=verbose', options=[ ('s', 'Short', 'key1'), ('m', 'Medium', 'key2'), ('l', 'Long', 'key3'), ('v', 'Verbose', 'key4'), ], sort=False) zipForm.add(form2.CheckboxWidget, 'reverse', '', title='Reverse:', hint='Reverse the sort order') zipForm.add(form2.CheckboxWidget, 'totals', 't', title='Totals:', hint='Show totals') zipForm.add_submit('Submit', 'Submit')
Explanation:
We use the add method of class form2.Form to create and add components to our form.
The arguments to add are the following:
klass -- The class of the widget that we wish to create. It should be one of the classes in form2.widget.
name -- The name of the widget. It is basically the CGI variable name. It is the value of the name attribute for the HTML element. We'll use this name to reference the widget within the form. For example, for the file_name widget:
zipForm.add(form2.StringWidget, 'file_name', '', title='File name:', hint='Enter zip file name', size=40)
the following name attribute is generated:
<input type="text" name="file_name" value="" size="40" />
value -- The initial value for the widget.
title -- A label to be used in the user interface.
hint -- Help information that is displayed in the user interface.
options -- For Radiobuttons and Select widgets, we give a list of options. The options can be a list of string values. However, by providing a tuple for each option containing (value, description, key), we get the form and widget to display the descriptions and to translate from selected option to the value for that object.
Extra keyword args are passed to the constructor method for the widget class.
The SelectWidget is an abstract class. Use SingleSelectWidget or MultipleSelectWidget.
Forms in form2 implement the dictionary interface. Therefore, you can retrieve data values that were entered in the Web browser by indexing the form instance with the name of the component. Here are some examples:
fileName = zipForm['file_name'] sortFlag = zipForm['sort_order'] columnFlag = zipForm['columns'] reverse = zipForm['reverse'] totals = zipForm['totals']
Explanation:
Use the render method to generate the HTML code for the form. In our example below, we are assuming that we are using PTL. That is that our code is in a .ptl file and that we have evaluated the following:
from quixote import enable_ptl enable_ptl()
Here is an example in which we create the form inside a cell of a 2-cell table with borders:
def example_ui [html] (self, request): header('Zip File Display') '<pre>\n' '%s' % content '</pre>\n' '<table border="2" width="100%">' '<tr><td>' zipForm.render() '</td></tr>' '<tr><td>' # Generate other data etc here. o o o '</td></tr>' '</table>' footer()
Explanation:
PTL is not the only way to generate HTML (or XHTML or XML) content within Quixote. This sections explores a few of those other options. In fact, you are free to use any method you can dream up to produce content to be returned to the client.
From your PTL function you can call a function implemented in plain Python, i.e. a function or method that does not have the "[plain]" or "[html]" modifier after the function/method name.
Use an HTML editor (or plain text editor to create content and store it in a file. From Python (PTL or not) read that file and return it as content.
This technique would enable you to use your favorite HTML editor such as Quanta, Bluefish.
There are a variety of template languages and processors for Python. If you do not already have a favorite, here is a good place to look: http://www.python.org/cgi-bin/moinmoin/WebProgramming. (Look for "Templating Systems".)
For example, you could use an HTML editor to create content and store it in a file. Then at run-time, read that content from the file, apply a template processor to it, and return the results for Quixote to be returned to the client.
In the following sub-section we'll consider the use of the Cheetah Template Engine.
Cheetah has a Python style. It uses "#" to mark Python code. It can be used as an independent template processor. And, for our purposes, it is especially good for generating mark-up content such as HTML and XML.
Here is a simple example of how you might use Cheetah to produce content for delivery by Quixote. Suppose that we have the following Cheetah template:
<html> <head> <title>A Cheetah Example</title> </head> <body> #for idx in range($count): <p>Hello, $name.</p> #end for </body> </html>
If this template is in a file name "test2.tmpl", we can compile it with:
cheetah compile test2.tmpl
which will produce a file named "test2.py" containing a definition for a class named "test2".
Then, we can import and use this template to generate content with something like the following:
/w1/Python/Cheetah/Test [152] python Python 2.3.3 (#1, Dec 21 2003, 13:10:04) [GCC 3.3.2 (Debian)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import test2 # [1] >>> values = {'name': 'bettylou', 'count': 5} # [2] >>> t = test2.test2(searchList=[values]) # [3] >>> content = str(t) # [4] >>> >>> print content <html> <head> <title>A Cheetah Example</title> </head> <body> <p>Hello, bettylou.</p> <p>Hello, bettylou.</p> <p>Hello, bettylou.</p> <p>Hello, bettylou.</p> <p>Hello, bettylou.</p> </body> </html>
Explanation:
You can learn much more about Cheetah, how to define templates, how to compile them, how to use them, etc from the documentation at: http://www.cheetahtemplate.org/learn.html.
You could also construct a DOM (document object model) of Python objects, the apply the PrettyPrinter to that DOM to produce XHTML.
But, then maybe this is doing things the hard way ...
Nevow is a package borrowed from Twisted. It provides a very Python-esque style for defining HTML elements and generating HTML code.
Work is just beginning in the Quixote community on Nevow. There is some initial work on an implementation for Nevow that is being coordinated on the Quixote email list. We'll see where this work goes.
Here is a tiny example of the use of Nevow:
document = html[ body[ form(action="")[ input(type="text", name="name") ], ] ]
Here are a few possibilities and combinations:
Place your HTML content in a file. From a python function, read that file. Use Python string interpolation to file values into this content. For example, if you have the following HTML in a file "test3.html":
<html> <head> <title>An interpolation Example</title> </head> <body> <p>Hello, %(name)s.</p> </body> </html>
Then, you can use the following to generate content:
>>> infile = file('test3.html', 'r') >>> s1 = infile.read() >>> infile.close() >>> values = {'name': 'davy'} >>> content = s1 % values >>> >>> print content <html> <head> <title>An interpolation Example</title> </head> <body> <p>Hello, davy.</p> </body> </html>
Same as the previous suggestion, but do it directly from a PTL function/method.
Use a similar string interpolation technique, but use a Python local variable to define the initial content.
Use Cheetah, but instead of compiling your template, read it from a file, create a Cheetah Template object, and convert it to a string. Here is an example that shows how to do this:
>>> from Cheetah.Template import Template >>> >>> infile = file('test2.tmpl', 'r') >>> s1 = infile.read() >>> infile.close() >>> >>> values = {'name': 'bettylou', 'count': 5} >>> t = Template(s1, searchList=[values]) >>> content = str(t) >>> >>> print content <html> <head> <title>A Cheetah Example</title> </head> <body> <p>Hello, bettylou.</p> <p>Hello, bettylou.</p> <p>Hello, bettylou.</p> <p>Hello, bettylou.</p> <p>Hello, bettylou.</p> </body> </html>
Use Cheetah and pre-compile your templates as described in the Cheetah sub-section above.
In this section we discuss some of the options available to improve or re-write the user interface that you have developed with Quixote and form2.
Here are a few of your options, each of which is discussed in a subsequence sub-section:
An alternative is to use form2 but to interact with and render individual widgets. Using this approach, we
Here is an example:
def do_show_contents [html](self, request): fileNameWidget = form.StringWidget('file_name', '', title='File name', size=40) sortOrderWidget = form.RadiobuttonsWidget('sort_order', 'File name', title='Column to sort by', options=['File name', 'Date/time', 'File size', 'Compressed size'], sort=False) columnsWidget = form.RadiobuttonsWidget('columns', 'Long', title='Columns to show', options=['Short', 'Medium', 'Long', 'Verbose'], sort=False) reverseWidget = form.CheckboxWidget('reverse', '', title='Reverse:', hint='Reverse the sort order') totalsWidget = form.CheckboxWidget('totals', 't', title='Totals:', hint='Show totals') submitWidget = form.SubmitWidget('submit') fileName = fileNameWidget.get_value() sortFlag = sortOrderWidget.get_value() columnFlag = columnsWidget.get_value() reverse = reverseWidget.get_value() totals = totalsWidget.get_value() content = '*** empty content ***' if fileName: outstream = StringIO.StringIO() try: zipls.listArchive(outstream, sortFlag, columnFlag, reverse, totals, fileName) content = outstream.getvalue() except IOError, exp: content = exp outstream.close() header('Zip File Display') '<table border="2" width="100%">' '<tr><td>' '<pre>\n' '%s' % content '</pre>\n' '</td></tr>\n' '<tr><td>\n' '<form action="show_contents" name="zip_form" method="post">\n' '<table border="0" width="100%">\n' '<tr>\n' '<td width="25%">FileName:</td><td width="75%">\n' fileNameWidget.render() '</td>\n</tr>\n<tr>\n<td>Sort order:</td><td>\n' sortOrderWidget.render() '</td>\n</tr>\n<tr>\n<td>Columns:</td><td>\n' columnsWidget.render() '</td>\n</tr>\n<tr>\n<td>Reverse:</td><td>\n' reverseWidget.render() '</td>\n</tr>\n<tr>\n<td>Totals:</td><td>\n' totalsWidget.render() '</td>\n</tr>\n<tr><td>\n' submitWidget.render() '</td></tr>\n' '</table>\n' '</form>\n\n' '</td></tr>\n' '</table>\n' footer()
Explanation:
We can create HTML user interfaces without using Quixote's form package at all. In order to do so, in PTL for example, do the following:
Explicitly produce mark-up for the HTML input items themselves, as well as some of the decoration around them.
Use the get_form_var method in the request object to get the value for each input item. For example:
value1 = request.get_form_var('arg1')
There are a variety of (sub-)strategies in this area. In general, we use Quixote and HTTP to provide access to the model, however, we implement the client user interface in Python, possibly with the wxPython or pygtk or PyQt GUI toolkit.
Here are a couple of ways to do this:
In this approach we take advantage of the fact that we implemented the original Quixote application with a strong separation between the user interface and the model. In fact, we implemented the model in a separate set of Python modules in a Python package. This enabled us to use the Python unit test framework to test the model separately. It also enables us to import and call the model from a separate application, which is what we are exploring in this sub-section.
How is it done? Write your user interface with, for example, wxPython or pygtk or PyQt, then, wherever in your application you need to interact with the model, import it and call it.
However, if you switch to any technology that is
Documentation on form2 is a bit slight. Here are examples of how to create and use the widgets in the form2 module:
from quixote.form2.widget import ButtonWidget # [1] class TestsUI: _q_exports = [] def do_test_form2 [html](self, request): testForm = form2.Form(name='test_form', action_url='test_form2', ) testForm.add(form2.StringWidget, 'string_widget', '[default string]', title='String widget:', hint='Enter a string.', size=40, # [2] required=False) testForm.add(form2.PasswordWidget, 'password_widget', '', title='Password widget:', hint='Enter a password.', size=40, required=False) testForm.add(form2.SingleSelectWidget, 'singleselect_widget', 'value2', title='Single select widget:', hint='Select a single value.', options=[ ('value1', 'Value #1', 'key1'), # [3] ('value2', 'Value #2', 'key2'), ('value3', 'Value #3', 'key3'), ('value4', 'Value #4', 'key4'), ], sort=False) testForm.add(form2.MultipleSelectWidget, 'multipleselect_widget', ('value3', 'value5'), title='Multiple select widget:', hint='Select multiple values.', options=[ ('value1', 'Value #1', 'key1'), # [3] ('value2', 'Value #2', 'key2'), ('value3', 'Value #3', 'key3'), ('value4', 'Value #4', 'key4'), ('value5', 'Value #5', 'key5'), ('value6', 'Value #6', 'key6'), ], sort=False) testForm.add(form2.RadiobuttonsWidget, 'radiobuttons_widget', 'value3', title='Radiobuttons widget:', hint='Select a radiobutton', options=[ ('value1', 'Value #1', 'key1'), # [3] ('value2', 'Value #2', 'key2'), ('value3', 'Value #3', 'key3'), ('value4', 'Value #4', 'key4'), ], sort=False) testForm.add(form2.CheckboxWidget, 'checkbox_widget1', 1, # [4] title='Checkbox widget 1:', hint='Unselected checkbox') testForm.add(form2.CheckboxWidget, 'checkbox_widget2', 0, # [4] title='Checkbox widget 2:', hint='Selected checkbox') testForm.add(form2.TextWidget, 'textblock_widget', 0, title='Text block widget 2:', hint='Enter text in text block.', rows= 6, # [5] cols=60, ) testForm.add_component(MyWidgetList, 'list_widget', value=['value1', 'value2', 'value3', 'value4'], title='List widget:', hint='WidgetList for test only', ) testForm.add(ButtonWidget, 'button_widget', 'Say hello', onClick='javascript: alert(\'Hello\')', # [6] ) testForm.add_submit('Submit', 'Submit') stringValue = testForm['string_widget'] # [7] passwordValue = testForm['password_widget'] singleselectValue = testForm['singleselect_widget'] multipleselectValue = testForm['multipleselect_widget'] radiobuttonsValue = testForm['radiobuttons_widget'] checkboxValue1 = testForm['checkbox_widget1'] checkboxValue2 = testForm['checkbox_widget2'] textblockValue = testForm['textblock_widget'] listValue = testForm['list_widget'] header('Zip File Display') '<table border="2" width="100%">' '<tr><td>' '<p>String value: "' # [8] stringValue '"</p><hr/>\n' '<p>Password value: "' passwordValue '"</p><hr/>\n' '<p>Single select value:' singleselectValue '</p><hr/>\n' '<p>Multiple select value:' multipleselectValue '</p><hr/>\n' '<p>Radiobuttons value:' radiobuttonsValue '</p><hr/>\n' '<p>Checkbox 1 value:' checkboxValue1 '</p><hr/>\n' '<p>Checkbox 2 value:' checkboxValue2 '</p><hr/>\n' '<p>Text block value: "' textblockValue '"</p><hr/>\n' '<p>List value:' listValue '</p>\n' '</td></tr>' '<tr><td>' testForm.render() # [9] '</td></tr>' '</table>' footer()
Notes (see numbered references above):
http://www.mems-exchange.org/software/quixote/: The Quixote support Web site.
Cheetah, the Python-Powered Template Engine
HTML Screen Scraping: A How-To Document: Information on how to extract data from HTML documents.