Infrastructure Perspective: Output Objects
Introduction
| O |
print "<table>" print "<tr>" print "<td>foo</td>" print "<td>bar</td>" etc.because it's very difficult to ensure proper balancing of tags, and thus very easy to generate incorrect HTML (the kind that some browsers render correctly and others don't).
Output objects provide a way to build an entire page dynamically, before sending a single line of output to the browser. This has several advantages. First, if at the last minute the script discovers an error, it can throw out the nearly-complete page and produce a new page which notes the error condition. Second, the page need not be constructed from the top to the bottom, but can be built piecemeal as the script gathers and processes information.
The Output Class Hierarchy
| E |
Rendering is triggered by calling the render_output(oo) function in the utils.output module, with an output object oo as argument. This function accepts output objects, lists of output objects, and plain old Python strings (which are returned verbatim). This makes it possible to include strings in the output without a special wrapper class.
The thing that makes Output objects so powerful is that most of them are containers. Container is a subclass of Output which adds a member variable, suboutputs. This variable contains a list of output objects that are "inside" the container. A container's render method concatenates the results of rendering each of the container's suboutputs. The Container class also has all the functions of a Python list, such as c.append(foo), len(c), c.insert(0, bar), etc.
Containers are not particularly useful for wrapping tags around some text: it's usually easier to just put three output objects into the parent container: the start tag, the text, and the end tag. (opposite day? NAR)
Containers are useful for more complicated formatting. The EnglishList class (in utils/output/html.py) provides an instructive example. This Container subclass formats its suboutputs as an English list, by putting commas and the word `and' between individual suboutputs. This class is a simple, complete abstraction of the rules for English lists, freeing other code from the burden of (re)implementing these rules.
List Elements
| T |
- DL
- The DL class makes a definition list. It wraps its suboutputs alternately in <dt> and <dd> tags.
- UL
- The UL class makes an unordered list. It inserts an <li> tag before each suboutput.
Form Elements
| T |
The FORM constructor takes a keyword argument, action=. If this argument is a string, it is used as the action attribute of the <form> tag. If it is a Display object, then the form will submit to the action URL for that object.
There are several Output subclasses representing form controls. All but one are subclasses of INPUT, the constructor for which takes a single argument, the attrs dictionary. The dictionary specifies the attributes of the generated tag. So, for instance, the tag <input type="text" name="foo" value="bar"> would be represented as TEXT_INPUT(attrs={'name' : 'foo', 'value' : 'bar'}). The subclasses are:
- TEXT_INPUT
- SUBMIT_INPUT
- RESET_INPUT
- RADIO_INPUT
- PASSWORD_INPUT
- IMAGE_INPUT
- HIDDEN_INPUT
- FILE_INPUT
- CHECKBOX_INPUT
- BUTTON_INPUT
Finally, the SELECT class creates <select> tags within a form. Its constructor takes the following keyword arguments:
- name
- The name attribute of the control.
- options
- A list of pairs (n, k) where n is the name (seen by the user) and k is the key (seen by the program).
- default
- The key or list of keys which should be selected initially.
- size The number of rows in a multiple select control.
- multi
- Boolean: true means this is a otherwise it's a
- attrs
- Any additional attributes for the <select> tag.
Stylistic Elements
| T |
Table Elements
| H |
The TABLE, TR, and TD classes all work together. The main attributes are specified to the TABLE constructor, which has the following keyword arguments:
- attrs
- The attributes for the <table> tag, as a dictionary.
- row_attrs and row_loop_start
- A list of attributes for the <tr> tags. The first list element is applied to the first row, the second to the second row, and so on. When the end of the list is reached, processing goes back to the list element with index row_loop_start. See below for an example.
- col_attrs and col_loop_start
- Like row_attrs, but apply to sequential <td> tags instead.
- sub
- Any pre-specified suboutputs (not usually used).
As an example of the use of this class, this Python code:
table = TABLE(
attrs = {'border' : 0, 'cellspacing' : 0 },
row_attrs = [ {'bgcolor' : '#808080'},
{'bgcolor' : '#FFFFFF'},
{'bgcolor' : '#EEEEEE'} ],
row_loop_start = 1,
col_attrs = [ { 'bgcolor' : '#808080' },
{ 'width' : '100' } ],
col_loop_start = 1)
table.append(TR( [ 'Foo', 'Bar', 'Bing', 'Baz' ] ) )
for i in range(10):
table.append(TR( [ 'foo-%d' % i, 'bar-%d' % i,
'bing-%d' % i, 'baz-%d' % i ] ) )
page.append(table)
made this table:
| Foo | Bar | Bing | Baz |
| foo-0 | bar-0 | bing-0 | baz-0 |
| foo-1 | bar-1 | bing-1 | baz-1 |
| foo-2 | bar-2 | bing-2 | baz-2 |
| foo-3 | bar-3 | bing-3 | baz-3 |
| foo-4 | bar-4 | bing-4 | baz-4 |
| foo-5 | bar-5 | bing-5 | baz-5 |
| foo-6 | bar-6 | bing-6 | baz-6 |
| foo-7 | bar-7 | bing-7 | baz-7 |
| foo-8 | bar-8 | bing-8 | baz-8 |
| foo-9 | bar-9 | bing-9 | baz-9 |
NestContainer
| O |
- Container:
- String: <h3>Faculty</h3>
- BulletColumnsContainer:
- Professor A
- Professor B
- Professor C
- etc.
- String: <h3>Masters in Computer Science Program</h3>
- BulletColumnsContainer:
- Instructor A
- Instructor B
- Instructor C
- etc.
- etc.
In this example, we have a BulletColumnsContainer nested within a Container. Most of the stuff that gets inserted into this nesting (people's names) ends up inside of a BulletColumnsContainer, but the occasional item (a headline) is outside of any BulletColumnsContainer, just in the Container.
We use NestContainer to accomplish this. A NestContainer object is constructed with a list of constructors for the different levels of containment. In the example above, it would be constructed like this:
nc = NestContainer(Container, BulletColumnsContainer)the object is then used like any other container (with the exception that it only supports the append method; this does not turn out to be a problematic restriction, and makes for more efficient code). When an object is inserted into the NestContainer (via nc.append(obj)), it is placed inside of a BulletColumnsContainer which itself is inside a Container which is inside whatever contains nc.
To convince NestContainer to insert an object higher up the containment hierarchy, use the NestContainer.Out(object, up) function. object is the object to be inserted, and up is the number of levels up through the containment hierarchy that the object should be inserted. So to insert the headlines in the example above (which are one level up from normal), we would use
nc.append(NestContainer.Out("<h3>Faculty</h3>", 1))
So the constructed containment hierarchy looks like this:
- NestContainer:
- NestContainer.Out("<h3>Faculty</h3>", 1)
- Professor A
- Professor B
- Professor C
- etc.
- NestContainer.Out("<h3>Masters in Computer Science Program</h3>", 1)
- Instructor A
- Instructor B
- Instructor C
NestContainer takes care of turning that into the hierarchy described above.
This class is hard to understand without seeing it in action. Check out how it's used in the people page.
The CSPage Container
| T |
The page object has a number of methods, useful for manipulating its appearance:
- page.set_type(t)
- Set the type, or section, that this page is in. t should be one of info, people, courses, research, or events.
- page.set_title(t)
- Set the title of the page (affects the <title> tag).
- page.add_navigation(url, text)
- Add a navigation link to url with name text.
- page.add_mode(url, text)
- Add a mode link to url with name text.
- page.add_help(url, text)
- Add a help link to url with name text. Help links are currently unused, but will appear in the yellow bar just like navigation and mode links.
Finding the Files
| A |

