Developing Componentized Web Applications in the Twisted Framework

DOMTemplate solves the problem of separating logic from presentation, and allows the templating logic to be expressed in Python code form using the DOM API. However, the DOM API is too low level and it quickly becomes tedious to use to build complicated HTML structures.

Twisted's Solution

Twisted's soluton is to provide a Model-View-Controller based component framework, which allows you to construct complex HTML "Views" out of many small interacting components, or "DOMWidgets".

Instead of manipulating DOM objects which represent low-level HTML Nodes, you construct and compose views using widgets defined in twisted.web.domwidgets, and higher-level widgets that you define yourself for an application-specific purpose.

Model-View-Controller

Model View Controller is simply a development strategy which involves breaking up your program logic into three seperate domains: Model objects, whose job it is to contain data; View objects, whose job it is to present this data to the user; and Controller objects, whose job it is to interpret the input the user provides and update the model and view with the user's desired changes.

Twisted's implementation of MVC is deliberately simple, and is defined in twisted.python.mvc. twisted.python.mvc takes advantage of twisted.python.components, the interface and component registry, in order to loosely couple the interacting objects. A complete MVC triad definition looks like this:

from twisted.python import mvc
from twisted.python import components

MFoo(mvc.Model): pass
VFoo(mvc.View): pass
CFoo(mvc.Controller): pass

# Register VFoo as a View for MFoo
components.registerAdapter(VFoo, MFoo, mvc.IView)
# Register CFoo as a Controller for MFoo
components.registerAdapter(CFoo, MFoo, mvc.IController)

Although it is not always necessary to define a complete MVC triad, I recommend doing so initially even if your class bodies are simply 'pass'. This will get you used to thinking about MVC objects as a complete triad of cooperating objects, and make it easy to add functionality to your triad later.

DOMTemplate and DOMController

The View and Controller objects that Twisted defines know nothing about the web; thus, twisted defines subclasses DOMTemplate and DOMController for interacting with twisted.web, Twisted's HTTP server. twisted.web defines IResource, the interface that Resource objects must implement in order to support publishing themselves on the web. DOMTemplate and DOMController both support this interface, giving you an easy and powerful way to handle incoming web requests and generate template-driven web pages.

Here is an example MVC triad for the web:

from twisted.python import wmvc


class MExample(wmvc.WModel):
    pass
class VExample(wmvc.WView):
    templateFile = 'Example.xhtml'
class CExample(wmvc.WController):
    pass


# Register VExample as a View for MExample
wmvc.registerViewForModel(VExample, MExample)
# Register CExample as a Controller for MExample
wmvc.registerControllerForModel(CExample, MExample)

While using Twisted's WebMVC you will be using all this boilerplate code for every page on your web site. It may seem excessive at first, but it has several advantages. By defining a complete MVC triad to represent a web page, you give your page three seperate namespaces for your web page's components; by following the MVC division of labor properly you make it easier to locate and isolate a piece of code based on what it does; and by componentizing your logic your code becomes far easier to reuse in other parts of your application, speeding development time.

Now, let's take a look at an actual application written in WebMVC. Here is a template that a designer has provided for the quote page on our site:

Listing 1: WebQuotes.html: Twisted Quotes Web Template

Notice that we have placed "class" and "id" attributes on those elements that we are interested in operating on. When DOMTemplate is iterating the template looking for nodes to operate on, any node with a class or id will be looked for in both the view and controller. If a node has the class or id "foo" the method factory_foo will be looked for in the view and controller.

These factory methods will be called with the request object and current node object as parameters. A view factory is expected to return a Widget subclass; there are many widgets in twisted.web.domwidgets that can be composed into larger widgets which are specific to your application. A controller factory, usually only relevant on an INPUT node, is expected to return an InputHandler subclass; these are defined in domhandlers.

Listing 2: webquoteresource.py: Twisted Quotes Web Resource module

So, to bring our template to life, we first override setUp to prepare the view for rendering. We then add factory_quote and factory_title methods on our view subclass. In the case of title we can simply return a domwidgets.Text instance. Since this factory was called in response to a node with the class of "title", when the node is rendered it will use the "title" attribute of the model as it's data. For our quote factory, we have implemented a custom widget. This makes our logic componentized and reusable. The same QuoteWidget could potentially be used to render multiple different pieces of data.

A DOMWidget's main responsibility is implementing generateDOM, which should return a DOM node heirarchy suitable for inserting into the tree. In this case, we take advantage of the higher level Widgets API and rely on domwidgets.Widget.generateDOM to do the work for us.

Finally, we provide a factory_newQuote method on the controller which returns a custom InputHandler subclass. There are a few methods handlers may override to deal with data, but in this case we override "check", which decides if the input the user enterred was valid, and "commit", which applies the user's input. For check, we simply see if the user entered anything at all; for commit, we append the newly entered quote to the end of the quotefile.

This is only the beginning of what can be done with Twisted's WebMVC modules. With a little extra work put into componentizing your logic into seperate domains, I think you will find that you will be creating more and more complex web sites with ease in no time.