Twisted Documentation: Page In Depth

# Page In Depth

Note:

This HOWTO documents the Page class, part of the Woven framework. The Woven framework should not be used for new projects. The newer Nevow framework, available as part of the Quotient project, is a simpler framework with consistent semantics and better testing and is strongly recommended over Woven.

The Woven documentation below is maintained only for users with an existing Woven codebase.

Page objects are the glue between a web request, a Model object, and a Template.

## Main Concepts

- Root Models are passed to Page objects when they are constructed. If no Model is passed, a Page will act as its own Root Model.
- Templates for Page objects can be specified in various ways.
- Child Pages for handling the next URL segment can be constructed using a convenient syntax supported by Page.
- Factories for View and Controller objects are contained in the Page. The View factories and Controller factories will be matched up with `view=` and `controller=` directives located in the template during the rendering process.

## Root Models

A Root Model is the base of a Woven Model tree. All Model data that the Page will use for rendering should be made available through this Model. Any Python object can be used as a Model within Woven, even Dictionaries, Lists, Strings, Integers, and Floats. This is accomplished through the use of IModel adapters, which normalize Model access methods.

A Root Model is passed to the Page constructor as the first argument:

    model = {'name': 'Donovan',
                   'interests': ['Computers', 'Music', 'Bling Bling']
                   }

    resource = page.Page(model)

If no Model object is passed as the first argument to the Page constructor, the Page object itself will be treated like a MethodModel. See Models for more information about Model objects.

Using a Page object as a Model factory (by not passing a Root Model) is discussed below in Factories.

## Templates

The Template a page will render can be specified in various ways.

- Passing `template="<html>Hello</html>"`
- Passing `templateFile="Template.xhtml"`
- Passing both templateFile and `templateDirectory="/Users/dsp/Templates"`

Every Page object must be able to find a Template in order to render itself. There are three main ways a template can be specified. The first is simply by passing the template as a keyword argument to the Page constructor:

    resource = page.Page(template="<html>Hello world!</html>")

However, it is desireable to store templates as separate HTML files on disk, where they can be edited easily by an external HTML editor. This can be accomplished by placing the template next to the .rpy script in the twisted.web directory and passing templateFile to the Page constructor:

    resource = page.Page(templateFile="MyTemplate.xhtml")

Finally, you may wish to place templates in a specific location, away from the python code entirely. To do so, pass both templateFile and templateDirectory to the Page constructor, indicating where you would like the template to be found:

    resource = page.Page(
      templateFile="MyTemplate.xhtml",
      templateDirectory="/Users/dsp/Templates")

If you are subclassing Page to provide child, model, view, or controller factories (discussed below), you may wish instead to specify a Page's template using a class attribute:

    class MyPage(page.Page):
        templateFile = "MyTemplate.xhtml"

A useful technique for storing your templates in a place where they are conveniently located next to your Python modules is to define templateDirectory as a class attribute, using Python's introspection abilities to discover where your Python module is located:

    class MyPage(page.Page):
        templateFile = "MyTemplate.xhtml"
        templateDirectory = os.path.join(os.path.split(__file__)[0], "templates")

How you manage your templates is up to you.

## Child Pages

As discussed in the Twisted Web section of the documentation, Resource objects provide access to their children by providing a getChild method which returns the next Resource object in the URL tree. Woven Page instances implement IResource and thus follow the same rules as any other Resource instance.

Woven Page instances can be built into Resource Trees in the same manner as regular Resources, using putChild. However, it is often convenient to construct a Page instance on the fly with a method.

Page instances with methods prefixed with `wchild_` will be invoked in getChild if there is a method matching the requested name. For example, if an instance of this class is used as the root Resource object, several URLs will be valid:

    class MyPage(page.Page):
        template = """<html>
            Root Page
            <p><a href="fred">Fred</a></p>
            <p><a href="bob">Bob</a></p>
        </html>"""

        def wchild_fred(self, request):
            return page.Page(template="<html>Fred!</html>")

        def wchild_bob(self, request):
            return page.Page(template="<html>Bob!</html>")

The following URLs would then be valid:

- `http://example.com/`
- `http://example.com/fred`
- `http://example.com/bob`
- `http://example.com/fred/`
- `http://example.com/bob/`

There is one special wchild method for when the URL ends in a slash. When `twisted.web` calls `getChild` with an empty string (when the URL ends in a slash), the wchild method that is called is `wchild_index`.

By default, all Page instances will attempt to add a slash to the end of the URL if they are visited through the web. Thus, visiting `http://example.com/fred` in the above example will redirect your browser to `http://example.com/fred/`. This generally makes writing relative links to other pages easier.

If this is not the behavior you would like, define the class attribute `addSlash` to be false:

    class MyPage(page.Page):
        addSlash = False
        template = "<html>No slash for you!</html>"

## Factories

So far, we have observed the use of the special `model=`, `view=`, and `controller=` attributes (directives) in Woven templates, but have not discussed how these attributes are translated to Python code.

There are three types of Woven factories:

1. View factories
2. Controller factories
3. Model factories

### View factories

When a view directive is encountered in a Woven template, it is translated into a `wvfactory_` call. For example, the node `<div view="cool" />` will cause the method `wvfactory_cool` to be called on your Page instance. View factories are methods which have the following signature, and must return an IView implementor:

    def wvfactory_cool(self, request, node, model):
            return widgets.Text(model)

Widget subclasses are the most common return value from `wvfactory_` methods. Widgets are discussed in the Views section. However, Page is also an implementor of IView, and you may take advantage of this to insert HTML fragments from other locations into a page which is being rendered:

    class MyPage(page.Page):
        template = """<html>
        <body>
            Some stuff goes here.
            <div view="header" />
            Some more stuff goes here.
            <div view="body" />
            Even more stuff goes here.
        </body>
    </html>"""

        def wvfactory_header(self, request, node, model):
            return page.Page(template="<div>This is the header.</div>")

        def wvfactory_body(self, request, node, model):
            return page.Page(template="<div>This is the body.</div>")

There is also a convenient special method, `wvupdate_`, which reduces the amount of boilerplate code required to quickly modify the template. `wvupdate_` methods have a slightly different signature; instead of being passed the DOM node which contained the `view=` directive, they are passed an instance of the generic Widget class, and they are passed the unwrapped Model data rather than the IModel wrapper:

    def wvupdate_red(self, request, widget, data):
            widget.setAttribute('style', 'color: red')

See the Views section for more information on writing your application's View code.

Note that if an appropriately named View factory is not found on your page class, Woven will look for the name in the `widgets` module before giving up and raising an exception. This is why we have been able to create templates that have nodes like `<div view="Text" />` and `<div view="List" />` without getting exceptions.

### Controller factories

When a controller directive is encountered in a Woven template, it is translated into a `wcfactory_` call. For example, the node `<input type="text" name="foo" controller="number" /<` will cause the method `wcfactory_number` to be called on your Page instance. Controller factories are methods which have the following signature, and must return an IController implementor:

    def wcfactory_number(self, request, node, model):
            return input.Integer(model)

The IController implementation classes which validate input currently live in the `input` module. This will probably be changed in the future.

See the Controllers section for more information on writing your application's Controller code.

Note that if an appropriately named Controller factory is not found on your page class, Woven will look for the name in the `input` module before giving up and raising an exception.

### Model factories

If your Page instance is passed a Root Model composed of basic python types, Woven is able to use IModel adapters to allow your Template to access the entire Model tree automatically. However, often it can be useful to produce your model data in some sort of method call which retrieves the data.

If your Page instance was initialized without a Root Model object, Woven will use the Page instance itself as a MethodModel. When a model directive is encountered in a Woven template, it will be translated into a `wmfactory_` call on your Page instance. For example, the node `<div model="name" />` will cause the method `wmfactory_name` to be called. Model factories are methods which have the following signature, and may return any Python object:

    def wmfactory_name(self, request):
            return "Fred Bob"

See the Models section for more information on writing your application's `IModel` adapters.

Index

Version: 1.3.0