Meet Stan

The Nevow Document Object Model

Introduction

Nevow (pronounced nou-veau) is a web development framework for Python. Yet another? Perhaps, but Nevow stands out from the crowd by providing a relatively simple, versatile, and incredibly powerful framework that encourages good design without constraining the developer to work in a particular way.

Brainchild of Donovan Preston of divmod.org, Nevow allows the developer to express as much or as little of the view logic of an application as desired directly in Python, whilst also providing a simple but complete XML tag attribute language to permit designer-edited templates. The framework cleanly separates the key processes of URL traversal, data manipulation, and data rendering. Other features of Nevow include a powerful Ajax implementation called LivePage and a system for exposing Python objects in web forms called formless.

The name Nevow relates to Nevow's origins as the new Woven, after the now-deprecated web development module from the twisted framework. Unlike Woven, Nevow is not tied to the twisted.web server and is designed for distribution in a range of deployment environments including CGI and WSGI (though there remain restrictions on the use of advanced features such as LivePage).

The popularity of Nevow has been somewhat limited, I believe, by the combination of a lack of good documentation and a rapidly evolving API. This has made it difficult for the beginner to get to that crucial point where the feeling of productivity kicks in. I have pieced together my understanding of Nevow from reading fragments of documentation and code, and browsing logs of the #twisted.web IRC channel. I hope that by sharing some of what I have learned I will encourage more Python fans to enter the wonderful world of Nevow.

This article is not a general introduction to Nevow, but an overview of Stan, the Nevow document object model. It is aimed at those who have a basic understanding of writing Nevow applications and want to learn more about harnessing the power of Stan. Some more general introductory material can be found on the Nevow wiki.

In order that you gain a good understanding of how Stan works 'under the bonnet' we will begin with the very basics, providing illustrative examples along the way. Some of this may seem a little tedious, but once you appreciate how Stan works you will find that web development with Nevow becomes quite intuitive, and more importantly very productive.

Interacting with Stan

A document object model (DOM) provides an infrastructure for building and manipulating documents programmatically. Stan is lighter than the more well-known W3C DOM; it is modeled using a tree structure built from nested sequences of basic Python types, in which the programmer may leave hooks to permit subsequent document manipulation.

The process of rendering a document from a Stan DOM is termed flattening because it involves converting the nested tree structure into a single 'flat' string. To demonstrate the basic idea of flattening a Stan tree, here is a rather contrived example using the interactive Python interpreter to flatten some Stan consisting of just lists, tuples, integers, and strings:

>>> from nevow import flat
>>> someStan = [1, " was a Stan tree", (" but now", " am flat.")]
>>> flat.flatten(someStan)
'1 was a Stan tree but now am flat.'

The flatten function in the flat module takes a chunk of Stan and iterates over the elements, recursively applying flattener functions that know how to render down an object of a specific type into something closer to a string. More precisely, a flattener function takes a specific type of object and returns either a string (indicating that the object has been completely flattened and no more work needs to be done) or one or more objects that Nevow knows how to flatten further. Flatteners are called recursively until all the objects in the DOM have been flattened and what remains is a single contiguous string. As the example above demonstrates, Stan has built-in flatteners for basic Python types.

What counts as Stan? Stan may include any Python object that has a registered flattener function. Nevow has built-in flatteners for Python sequences, meaning that Stan may include any sequence of Python objects that have registered flattener functions, and sequences may be nested.

One of the things that makes Stan appealing to the Python programmer is the crucial design decision to implement it in such a way that the Python interpreter already knew how to parse it! Not only does this save us from learning one more half-baked templating language, it also means that all the power of Python is available directly within the DOM. Paradoxically, we will see that one of the key features of Nevow is the facility to cleanly separate program logic from document templates. And here is what makes Nevow the Python web framework for 'consenting adults': with Nevow good design becomes natural, but unnatural design remains possible.

The Tag class

To facilitate modeling of XHTML-style documents, the stan module provides the Tag class. Instances of the Tag class can be included directly in the Stan tree because there is a built-in flattener that converts Tag instances into XML-style tag strings.

Tag instances have an attributes dictionary and a children list. The attributes dictionary is a mapping of {name: value} pairs corresponding to XHTML tag attributes and the children list is a sequence of Python objects that are descendants of the current Tag. Children may be other Tag instances, strings, functions, or any other object which has a registered flattener.

The Tag class cunningly abuses the __getitem__ and __call__ special methods so that you can initialise and modify Tag attribute values by calling Tag instances with keyword-style arguments and you can append children to Tag instances using square bracket notation. Multiple children may be appended by passing a tuple or a list in square brackets. The Tag class also defines a clear() method that deletes all children from the tag.

This will become clearer with an example:

>>> from nevow import flat, stan
>>> html = stan.Tag('html')
>>> p = stan.Tag('p')
>>> someStan = html[ p(style='font-family: Verdana;')[ "Hello, ", "world!" ] ]
>>> flat.flatten(someStan)
'<html><p style="font-family: Verdana;">Hello, world!</p></html>'

First we constructed Tag instances corresponding to the '<html></html>' and '<p></p>' XHTML tags. Then we used them to create a fragment of Stan, assigning one attribute (the 'style' attribute) and two children (the strings "Hello " and "world!") to the Tag instance p. When we flattened the Stan the Tag instances were rendered as XHTML tags.

For the most part, Tag attributes can be given exactly the same name as the corresponding XHTML attribute. Unfortunately there are a few clashes between Python keywords and XHTML attribute names. Wherever this is the case, the Stan attribute name should be postfixed with an underscore. The underscore will be removed from the attribute name during flattening. For example, the 'class' attribute clashes with the Python keyword class, so use class_ instead.

We have now seen how Stan can model XHTML, but constructing Tag instances each time we need them seems tedious. This is where the tags module comes in.

The tags module

The tags module provides a set of factory-like prototype Tag instances corresponding to the tags specified in the XHTML standard. Using the tags module we can easily model a complete XHTML document in Stan:

from nevow import flat, tags
aDocument = tags.html[
                tags.head[
                    tags.title["Hello, world!"]
                ],
                tags.body[
                    tags.h1[ "This is a complete XHTML document modeled in Stan." ],
                    tags.p[ "This text is inside a paragraph tag." ],
                    tags.div(style="color: blue; width: 200px; background-color: yellow;")[
                        "And this is a coloured div."
                    ]
                ]
            ]
print flat.flatten(aDocument)

Outputs:

<html><head><title>Hello, world!</title></head><body><h1>This is a complete ...

In this example we took advantage of the Python parser's handling of whitespace to lay the Stan tree out in a more readable indented format, making it easier to check that opening and closing brackets are paired off correctly.

It may be tempting to use from nevow.tags import * so that you can refer to tags directly without using module attribute notation, but it is generally best to avoid this practice because it clutters up the module namespace and risks creating confusing bugs (remember that HTML includes single-letter named tags such as a, b, i, and p, which are frequently used as names for temporary variables). A better compromise is to write from nevow import tags as T, allowing you to use the shortcut notation T.html[ T.p ].

Entity references

The XHTML specification provides named and numbered entity references for commonly used special characters. The ampersand, for example, is a reserved character in XHTML, necessitating that you use the '&amp;' entity reference name to insert an actual ampersand character in your document.

Stan models entities with the Entity class and, as you may have guessed, Nevow has a built-in flattener function for Entity instances. As with the Tag class, you will not need to use the Entity class directly because Nevow provides predefined XHTML entity references in the entities module:

>>> from nevow import flat, entities as E
>>> flat.flatten([E.amp, E.gt, E.lt, E.quot])
'&amp;&gt;&lt;&quot;'

This flattened string will be rendered in a browser like so:

& > < "

Pretty-printing flattened Stan

As you may have noticed when we flattened aDocument above, the XHTML output from Nevow is not designed for human consumption. Unlike its forebear HTML, XHTML preserves whitespace and whitespace can affect how a browser lays out a page, so Nevow does not make any assumptions about tag layout. Generating nicely indented XHTML can, however, be useful for testing and also to facilitate conversion of Stan syntax to XML Templates, for the HTML monkeys. A simple way of achieving this is to use uTidylib, a Python wrapper for the TidyLib library underlying the fabulous HTML Tidy utility:

import tidy

def prettyXHTML(uglyXHTML):

    """Convert ugly XHTML to pretty XHTML using uTidylib."""

    options = dict(input_xml=True,
                   output_xhtml=True,
                   add_xml_decl=False,
                   doctype='omit',
                   indent='auto',
                   tidy_mark=False)
    return str(tidy.parseString(uglyXHTML, **options))

print prettyXHTML(flat.flatten(aDocument))

Outputs:

<html>
  <head>
    <title>Hello, world!</title>
  </head>
  <body>
    <h1>This is a complete XHTML document modeled in Stan.</h1>
    <p>This text is inside a paragraph tag.</p>
    <div style="color: blue; width: 200px; background-color: yellow;">
    And this is a coloured div.</div>
  </body>
</html>

Beautiful, no? Just remember that if you intend to use pretty-printed output for anything other than testing you will need to double-check that the browser renders it as you expect. With only these basics under your belt, Stan is already a very useful tool for programmatically generating static XHTML documents.

Getting dynamic: Functions in the DOM

So far we've seen how Stan uses some clever tricks to model static XHTML documents, but what we really want is to build dynamic web pages. The secret to dynamics is that Nevow has built in flatteners for Python callables (functions and methods). Thus the Stan tree can be generated and manipulated at runtime.

Function and method flatteners work by calling the referenced object at render time, passing the current context as the first argument and the current data as the second argument [1]. The return value of the call is inserted in the DOM, at the position where the function was embedded. The context argument is one of the more mysterious aspects of Nevow, and alas it shall remain so here, but tautologically speaking it is for passing ‘contextual stuff’. We shall learn more about the data argument later. The important thing to note at this stage is that the tag attribute of the context object passed to a function refers to a clone of the containing Tag instance in the DOM. This makes it possible to inspect and manipulate the containing tag.

Here is an example of a function in the DOM:

from nevow import flat, tags as T

def tagInserter(context, data):
    return T.p[ "I was inserted at runtime." ]

someStan = T.div[ tagInserter ]
print flat.flatten(someStan)

Outputs:

<div><p>I was inserted at runtime.</p></div>

As you can see, the return value of tagInserter(context, data) replaced the tagInserter reference in the DOM.

Note that a function in the DOM will actually be passed a reference to itself, inside context.tag. Thus, returning context.tag as-is will create a recursion in the DOM. Don't worry, functions are not normally used quite like this - this discussion is intended only to clarify what is actually going on under the bonnet of Stan. We will get to some more interesting examples soon.

Tag special attributes

Though we are free to insert functions in the DOM anywhere we like, it is strongly encouraged that we take advantage of some special hooks that Stan provides for adding dynamic content. These hooks cleanly separate the dynamics of data retrieval and manipulation (business logic) from the dynamics of rendering layout (view logic). As is the Python way, Nevow does not enforce this design principle and there is nothing stopping you from performing database lookups or complex calculations inside render functions but if you adhere to this schema your programs will be more flexible and maintainable in the long run.

Function hooks are exposed as special attributes of the Tag class, referred to as Tag specials. These attributes are not output when a tag is flattened. The most important tag special attributes are:

render

If a value is assigned to the render special attribute then it will be treated as Stan and flattened at runtime. The flattened result will replace the current tag in the DOM. It is intended that the render attribute be assigned a callable which, as discussed earlier, will be flattened by calling it with (context, data) arguments. Remember that the value of context.tag is set to a clone of the current tag so that we may manipulate it and return it to the DOM. The render special attribute of the cloned tag will not be set, avoiding the problem of accidental recursion touched upon in the previous section. If you want a render method to return nothing, then return an empty string. The default return value of a Python function (None) will be rendered as an error.

Here is a very basic example of using a render function to modify the current tag:

from nevow import flat, tags as T

def tagMutator(context, data):
    return context.tag[ "I was modified at runtime." ]

someStan = T.div(render=tagMutator)
print flat.flatten(someStan)

Outputs:

<div>I was modified at runtime.</div>

data

The data special attribute is a little magical. The attribute may be assigned any Python object. If there is an adapter for this object that implements the interface inevow.IGettable, which specifies a single method get(context, data), then get is called to get data from the object. Otherwise, if no adapter is defined, the object itself becomes the new data. The data applies to the current tag and all descendant tags that are not explicitly passed new data. As we have seen, the current tag's data is passed as the second argument to any functions or methods bound to the render attribute.

Most objects will not be adapted via IGettable (unless you define an adapter yourself) but notably Nevow does have built-in IGettable adapters for callables. The get method of the adapter calls the function or method at runtime with the arguments (context, data) and the current data takes the return value. Here is an example that illustrates direct data assignment (with a string) and data assignment via an IGettable adapter (with a function):

from nevow import flat, tags as T

def data2string(context, data):
    return context.tag[ str(data) ]

def getSomeData(context, data):
    return "My data was got at runtime, via IGettable."

someStan = T.div[
               T.p(data="My data was passed as-is.", render=data2string),
               T.p(data=getSomeData, render=data2string)
           ]
print flat.flatten(someStan)

Outputs:

<div><p>My data was passed as-is.</p><p>My data was got at runtime, via IGettable.</p></div>

macro

The macro attribute specifies a function that will be called once in the lifetime of the template, with (context, data) arguments. The return value will replace the current tag in the template. Macros are an optimisation feature that can reduce the amount of work needed to render a page in the case where some elements can only be calculated at runtime, but will not subsequently change.

pattern

The pattern attribute value names a pattern tag for later retrieval. Pattern tags are discussed later.

key

The key attribute can be used to give a tag a unique label string. This is label is automatically namespaced, so in T.span(key='foo')[T.span(key='bar')] the inner span actually has a key of 'foo.bar'. According to the source code, the key attribute is intended to provide tags a unique label that can be used to generate things like XHTML 'id' attributes.

Tag special directives

Stan includes a directive class for passing special instructions to data and render attributes. Nevow has a built in flattener and a built in IGettable adapter for directive instances so that directives may be passed to both render and data specials.

The flattener for directives passed to the render special attribute works by looking for something implementing the inevow.IRenderFactory interface in the current context. A RenderFactory takes a directive name and returns a callable responding to the (context, data) arguments. The callable should return some Stan.

The IGettable adapter for directives passed to the data special attribute works by looking for an inevow.IContainer adapter for the current data. IContainer specifies a single method child(context, name) that should return some data from an object by name. An IContainer adapter returns None if the adapted object does not have the requested child.

Nevow provides IContainer adapters for Python sequences (lists and tuples) and dictionaries. If the current data is a sequence then data attributes of child tags with numeric names will retrieve the corresponding elements of the sequence. If the current data is a dictionary then nested data attributes assigned a key name will retrieve the corresponding value. For example:

from nevow import flat, tags as T, rend
popStyles = dict(bob='reggae', kurt='grunge')
def data2string(context, data):
    return context.tag[ str(data) ]
someStan = T.div(data=popStyles)[
               T.p(render=data2string),
               T.ul[
                   T.li(data=T.directive('bob'), render=data2string),
                   T.li(data=T.directive('kurt'), render=data2string)
               ]
           ]
print flat.flatten(someStan)

Pretty-printed output:

<div>
  <p>{'kurt': 'grunge', 'bob': 'reggae'}</p>
  <ul>
    <li>reggae</li>
    <li>grunge</li>
  </ul>
</div>

In this example, the data for the containing 'div' tag is a dictionary. The 'li' child tags get their data using directives that specify keys in the data dictionary. These keys are resolved to the corresponding values by the built-in IContainer for dictionaries.

ObjectContainer

The accessors module includes a basic implementation of IContainer called ObjectContainer that can adapt any Python object to the IContainer interface, allowing attribute lookup by name. The Python convention of beginning object private attribute names with an underscore is respected. Here is an example:

from nevow import accessors, compy, flat, inevow, tags as T

class User(object):
    def __init__(self, name, email, password):
        self.name = name
        self.email = email
        self._password = password
compy.registerAdapter(accessors.ObjectContainer, User, inevow.IContainer)
user = User('fzZzy', 'fzZzy@smallfurryanimals.xxx', 'unbec0m1ng')

def data2string(context, data):
    return context.tag[ str(data) ]

someStan = T.table(data=user)[
               T.tr[
                   T.td[ "Name:" ],
                   T.td(data=T.directive('name'), render=data2string)
               ],
               T.tr[
                   T.td[ "Email:" ],
                   T.td(data=T.directive('email'), render=data2string)
               ],
           ]
print flat.flatten(someStan)

Pretty-printed output:

<table>
  <tr>
    <td>Name:</td>
    <td>fzZzy</td>
  </tr>
  <tr>
    <td>Email:</td>
    <td>fzZzy@smallfurryanimals.xxx</td>
  </tr>
</table>

An attempt to retrieve the _password attribute of a User instance by directive will result in a ValueError exception being raised.

You may be wondering what the point of directives is. Why do we not just pass data and callables directly to data and render special attributes? Certainly we could, but directives provide a valuable abstraction layer. Data directives, via IContainer, isolate the implementation of your objects from the data extraction process so that you have control over what object attributes are exposed and how they are referenced. Both data and render directives provide a layer of syntactic sugar that will become useful when we learn about building web pages with the Page class.

Slots

Slots are placeholders in the document template that are dynamically replaced by Stan specified at runtime. Slots provide a means of manipulating a set of descendant tags from the render special of a single parent tag, avoiding having to implement render specials for each child tag. When combined with patterns, slots greatly simplify the process of generating recurring patterns in a document.

Stan tags have a fillSlots(slotName, slotValue) method that allows a tag to remember a value for a particular slot name so that any descendant slot tags named slotName are are rendered as slotValue. To demonstrate:

from nevow import flat, tags as T
someStan = T.html[
             T.h1[ T.slot(name='foo') ],
             T.p[ T.slot(name='foo') ]
           ]
someStan.fillSlots('foo', T.span["I was a 'foo' slot."])
print flat.flatten(someStan)

Outputs:

<html><h1><span>I was a 'foo' slot.</span></h1><p><span>I was a 'foo' slot.</span></p></html>

As this example demonstrates, fillSlots fills all slots with the given name, not just the first. Note that Nevow requires that slots be filled - an empty slot will cause an exception during flattening.

Patterns

Patterns are named Stan tags designed to be cloned and reused as needed in the DOM. It is best to think of patterned tags as invisible tag blueprints, hidden in the DOM for later use. This is an important concept: unless retrieved and explicitly introduced into the DOM, patterned tags will not show up when the Stan is flattened.

Pattern names may be shared by more than one Tag instance. As we will see, this makes it possible to generate cycling patterns, such as you might use to create a table with alternating row styles.

Tag instances have the following methods for retrieving descendant pattern tags:

onePattern(name)

Get a single clone of a pattern tag with a unique name. If there is more than one matching pattern or no matching patterns, an exception is raised. This is useful for generating a uniformly repeating pattern.

allPatterns(name)

Get a list of clones of pattern tags matching the given name, in the order that they are defined in the template. This is useful if you want to insert them all in one place.

patternGenerator(name, default=None)

Get a PatternTag (a factory for clone tags) which will generate clones of matching pattern tags forever, cycling through the tags in the order they are defined, and looping around to the beginning when all matching patterned tags have been returned. If no matches are found, and default is None, an exception is raised. Otherwise this method generates clones of default forever. This is useful to find repeating pattern elements.

Cloned pattern tags have the pattern special attribute removed and can be used just like normal tags. A PatternTag is similar except for the small gotcha that it only generates a valid Stan tag when either (a) it is called, or (b) it has Stan appended to it (i.e. using square-bracket notation). You cannot include a PatternTag in the DOM as-is.

A simple example, combining slots and patterns:

from nevow import flat, tags as T

dogs = ["German Shepherd", "Labrador", "Terrier"]
def dogLister(context, dogs):
    pat = context.tag.patternGenerator('list_item')
    for dog in dogs:
        context.tag[ pat().fillSlots('dog_type', dog) ]
    return context.tag

someStan = [ T.h1[ "Some Dogs" ] ],
             T.ul(render=dogLister, data=dogs)[
                 T.li(pattern='list_item')[ T.slot(name='dog_type') ]
             ]
           ]
print flat.flatten(someStan)

Outputs:

<h1>Some Dogs</h1><ul><li>German Shepherd</li><li>Labrador</li><li>Terrier</li></ul>

Stan and the Page class

Now that we have a reasonable overview of the mechanics of Stan it is time to see how we can use this knowledge to build dynamic web pages. The Page class in the rend module unifies Stan templating, URL parsing, and a number of other processes into an object that loosely represents a single page in a web application. The URL parsing features of Page are discussed in Donovan Preston's introduction to Object Traversal.

Page has some special class attributes that determine basic functionality. The docFactory attribute (which can either be a class or an instance attribute) determines the template used for the page. Page is designed to handle page templates in a variety of formats, including Stan syntax and XML templates. The loaders module provides a uniform wrapper for the different kinds of templates. Thus we do not assign Stan directly to the docFactory attribute, but instead with the Stan loader.

This is best demonstrated with an example:

>>> from nevow import rend, loaders, tags as T
>>> class HelloWorld(rend.Page):
...     docFactory = loaders.stan(T.html[ T.p[ "Hello, world!" ] ])
>>> print HelloWorld().renderSynchronously()
<html><p>Hello, world!</p></html>

First we created a subclass of Page and assigned some simple Stan to the docFactory class attribute via the loaders module. To flatten a Page we use the method renderSynchronously(), rather than using the flat module directly.

Page class method naming conventions

You may recall that we said Stan directives permit some syntactic sugar when using the Page class. Let us start with an example:

from nevow import rend, loaders, tags as T

class TestPage(rend.Page):

    docFactory = loaders.stan(
        T.html(render=T.directive('name'),
               data=T.directive('name'))
    )

    def data_name(self, context, data):
        return "Johnny"

    def render_name(self, context, name):
        return context.tag[
            T.span(style='color: blue;')[ name ]
        ]

print TestPage().renderSynchronously()

Outputs:

<html><span style="color: blue;">Johnny</span></html>

As you can see we defined data_ and render_ methods on our subclass of Page and these were automatically discovered by the correspondingly named directives on the tag specials in the page template.

Built in renderers of the Page class

Page implements a number of render methods to simplify common rendering operations. Though a tag render special attribute can be passed one of these render methods explicitly (e.g. render=self.render_name, provided docFactory is an instance attribute) it is recommended that you use the directive syntax (e.g. render=T.directive('name')), which uncouples the tag special from the Page instance and allows the Page class control over the call (permitting such things as parameterisation of render functions with render=T.directive('name third_arg,fourth_arg')).

render_data

Renders the current data as-is inside the current Tag. The data will then be treated as Stan, and flattened further as necessary. The clear() method is called on the current tag, so all existing children are removed. For example:

from nevow import loaders, rend, tags as T

class TestPage(rend.Page):
    docFactory = loaders.stan(
        T.html(data=T.directive('example'), render=T.directive('data'))[
            "This will be cleared."
        ]
    )
    def data_example(self, context, data):
        return ["Data rendered by the rend.Page render_data method: ",
                (1,2,3),
        ]

page = TestPage()
print page.renderSynchronously()

Outputs:

<html>Data rendered by the rend.Page render_data method: 123</html>

render_sequence

This renderer is very useful for generating repeating patterns. It iterates the current data, appending a clone of the tag pattern named 'item' for each element. The data special attribute of the cloned tag is set to the current data element, so that it is available to the tag renderer. Although only the 'item' pattern is required, the sequence renderer is quite smart, recognising the following patterns:

  • header

    This pattern is rendered at the start, before the first item. If multiple header patterns are provided they are rendered together in the order that they are defined.

  • footer

    Similar to the header, but rendered at the end, after the last item.

  • item

    This pattern is required by the sequence renderer, and is rendered once for each item in the sequence. If multiple item patterns are provided then the patterns are used in cycle, in the order that they are defined.

  • divider

    This pattern is rendered once between each item in the sequence. If multiple divider patterns are defined then they are cycled.

  • empty

    This pattern is rendered instead of item and divider patterns in the case that the sequence data is empty.

Here is an example that generates a table listing files by name and size, with a header and alternating row patterns:

from nevow import loaders, rend, tags as T

class FileLister(rend.Page):
    docFactory = loaders.stan(
        T.html[
            T.table(render=T.directive('sequence'))[
                T.tr(pattern='header')[ T.th[ 'Filename' ], T.th[ 'Size' ] ],
                T.tr(pattern='item', class_='odd', render=T.directive('row')),
                T.tr(pattern='item', class_='even', render=T.directive('row'))
            ]
        ]
    )
    def render_row(self, context, aFile):
        return context.tag[ T.td[ aFile[0] ], T.td[ aFile[1] ] ]

someFiles = [
    ('first.file', 1283),
    ('second.file', 92932),
    ('third.file', 12933),
]
page = FileLister(someFiles)
print page.renderSynchronously()

Pretty-printed output:

<html>
  <table>
    <tr>
      <th>Filename</th>
      <th>Size</th>
    </tr>
    <tr class="odd">
      <td>first.file</td>
      <td>1283</td>
    </tr>
    <tr class="even">
      <td>second.file</td>
      <td>92932</td>
    </tr>
    <tr class="odd">
      <td>third.file</td>
      <td>12933</td>
    </tr>
  </table>
</html>

This example also illustrates that if Page.__init__ is passed an argument then it becomes the default data for the template.

render_mapping

The mapping renderer lets you quickly fill a set of slots using a dictionary-like mapping as data. The renderer loops over the sequence returned by the items() method of the current data, calling the fillSlots(key, value) method on the current tag for every {key: value} pair. For example:

from nevow import loaders, rend, tags as T

class SlotMapper(rend.Page):
    docFactory = loaders.stan(
        T.html(render=T.directive('mapping'))[
            T.p[ T.slot(name='foo') ],
            T.p[ T.slot(name='bar') ]
        ]
    )
slotMap = dict(foo="I am in the foo slot.", bar="I am in the bar slot.")
page = SlotMapper(slotMap)
print page.renderSynchronously()

Outputs:

<html><p>I am in the foo slot.</p><p>I am in the bar slot.</p></html>

render_xml

An uncommonly used renderer that inserts the current data into the template after wrapping it in an xml tag.

Custom flatteners

In most cases the recommended way of handling rendering of your own objects is to provide explicit data_ and render_ functions. Data functions marshal object attributes into render functions which are responsible for incorporating this information into the DOM. Not to ties your hands, Nevow also makes it possible to add custom flatteners for your own objects, which may then be included directly in the DOM and rendered magically. This technique can be very powerful, though should be used with caution because it can make code more difficult to read and maintain.

A flattener function takes the object to be flattened as the first argument and optionally the current tag context as the second argument. The function should return either a string - the fully flattened representation of the object - or else any valid Stan that is closer to being flattened (i.e. further calls to flatteners will ultimately result in a string). For Nevow to know about a custom flattener it must be registered using the registerFlattener(flattener, targetType) function of the flat module.

For example, here we define a custom flattener to automatically render instances of a User class as a table listing name, email, and password attributes:

from nevow import flat, tags as T

class User(object):
    def __init__(self, name, email, password):
        self.name = name
        self.email = email
        self.password = password

def flatten_user(original, ctx=None):
    return T.table[
               T.tr[
                   T.td[ "Name:" ],
                   T.td[ original.name ]
               ],
               T.tr[
                   T.td[ "Email:" ],
                   T.td[T.a(href='mailto:%s' % original.email)[ original.email ] ]
               ],
               T.tr[
                   T.td[ "Password:" ],
                   T.td[ "******" ]
               ],
           ]
flat.registerFlattener(flatten_user, User)

print flat.flatten(User('fzZzy', 'fzZzy@smallfurryanimals.xxx', 'unbec0m1ng'))

Pretty-printed output:

<table>
  <tr>
    <td>Name:</td>
    <td>fzZzy</td>
  </tr>
  <tr>
    <td>Email:</td>
    <td><a href="mailto:fzZzy@smallfurryanimals.xxx">fzZzy@smallfurryanimals.xxx</a></td>
  </tr>
  <tr>
    <td>Password:</td>
    <td>******</td>
  </tr>
</table>

Now you can include a list of User instances directly in the DOM and Nevow will know exactly what to do with them when it comes time to render the page. Black magic.

Some Stan extras

This section contains a few odds and ends in the the tags module that you may occasionally find a use for in your Stan templates:

  • The invisible tag is a special instance of the Tag class that is rendered as an empty string. It is useful when you want a tag to bind tag specials to, but don't want the tag itself to leave any trace in the final document.
  • The comment tag allows you to insert XHTML comments with the syntax tags.comment['This will be flattened to a comment.']
  • There are predefined directive instances in the tags module for integers in the range 1-100, to facilitate passing sequence indices to data specials. As Python names may not start with a number they are prefixed with an underscore (e.g. tags._42 is equivalent to tags.directive('42')).

The following are initialised by passing a string as the only argument:

  • A string passed to the inlineJS tag is assumed to be valid javascript and is flattened into inline '<script type="text/javascript"></script>' tags.
  • xml instances are designed to allow inclusion of valid XML strings directly in the DOM. During the flattening process strings are normally escaped to make them XML-safe and then encoded as utf-8. The content of an xml instance is assumed to be XML-safe and directly encoded as utf-8.
  • cdata instances should contain string data that is to be utf-8 encoded and included in a '<![CDATA[]>' XHTML tag.
  • raw instances contain content that should be unaltered during the flattening process. It is very unlikely that you actually want to use these; in nearly all cases you will want Nevow to handle at least Unicode encoding for you.

XML templates

Much can be achieved with Stan syntax alone, but the application developer frequently wants to be able to pass the design aspects of the development process on to a web designer who prefers to work with HTML. Nevow's Tag Attribute Language is an XML namespace that enables you to include directives corresponding to Nevow's special tags and attributes directly in XHTML templates. Donovan Preston has written an introduction to the Nevow XML templating system.

There are no hard and fast rules about what portions of your application should be implemented using Stan syntax and what should be implemented with XML templates. Using the techniques discussed here, it is easy to start modeling your documents with Stan syntax and then later convert portions to XML as required. I have actually found that the more I have worked with Stan the more reluctant I am to cede to XML templating, instead preferring liberal application of 'class' and 'id' tag attributes to allow layout to be later manipulated with CSS.

Where to from here?

In the interests of brevity the examples given here have been designed to illustrate principals rather than to provide fully functional solutions to commonly encountered problems. The Nevow distribution comes with a set of more extended examples that will help you to get started developing real web applications. The examples are incompletely documented, but with a firm grasp of the principals of Stan you will find it much easier to follow the source code. The examples are (transiently) available online at http://nevowexamples.adytum.us/.

Conclusion

Nevow is a very flexible framework for web application development in Python that offers many powerful features but has suffered from a lack of documentation and a rapidly evolving API. Now that the API is beginning to congeal ;) what Nevow needs most is good set of trainer wheels to get the beginner up and rolling. Stan, a pure Python document object model, is one of Nevow's finest features and deserves wider attention in the Python web development community.

Acknowledgments

I would like to thank all of the twisted/Nevow developers for their fine work.

Appendix 1: Document History

  • 6 July 2005: First draft.
  • 10 July 2005: Minor amendments, thanks to feedback from Matt Goodall.
[1]The IRenderFactory implementation should transparently take care of passing self to methods.