Introducing Twisted Web Widgets

twisted.web.widgets is being gradually deprecated in favour of Woven. See the module docstring for details.

Introduction

This is more of a simple description of all the classes, plus the common pitfalls of coding in Web Widgets. Oh well.

Example Code

Chris Armstrong has made some example (contrived) Widgets code available, at http://twistedmatrix.com/users/carmstro.twistd/files/Example.tar.gz. Unpack it into your ~/TwistedPlugins / directory and run twistd -g Example somewhere to start the server on localhost:8080. Please read the code (comments) before getting confused about the odd behavior of the example server -- note that you are supposed to get a No Resource error on the root URL (http://localhost:8080/) when you first load it up; the code explains this.

The Diagram

The Classes

Gadget

A collection of widgets, like a directory of HTML files. You add widgets to it with self.putWidget("name", WidgetInstance()). This widget will be rendered inside the Gadget-local page. Also, if you make a Gadget that is also a subclass of Widget, then whenever the index (http://foo.com/foo/, foo being the Gadget/Widget resource) is requested, the object will be rendered as a Widget inside of the Gadget-local page factory. The Gadget-local page factory is the 'pageFactory' attribute of the gadget, which should be a class that takes a widget in it's constructor, and displays that Widget in some form. So in your __init__ method for your Gadget subclass, do self.pageFactory = SomeWidgetPageSubclass (see WidgetPage below) (note that it is not an instance, but the actual class object).

Widget

A Widget is simply something that is renderable, through its display() method. This method is expected to return a list of HTML strings. (it can also contain instances of defer.Deferred -- but this is another story).

Presentation

This is a special Widget that already has a display() method, which renders some objects through a template. You override the special 'template' variable, which is a string with interpolated python expressions. It should look something like:

template = '''\
    <html>
    <head><title>%%%%self.title%%%%</title></head>
    <body>%%%%self.getContent(request)%%%%
    </body></html>
'''

As you can see, Python expressions are denoted with surrounding sets of 4 %s. The expressions are evaluated in a special namespace with only 'self' and 'request' in it.

WidgetPage

A WidgetPage is a special Page/Presentation combination that allows you to pass a Widget object to its constructor. The most common use of this class is for subclassing; you should have a subclass that defines a custom 'template' attribute. WidgetPage stores the widget you pass to it in it's 'widget' attribute, so remember that whenever you're making a customized template, use %%%%self.widget%%%% to access it (see Common Pitfalls: WidgetPage below).

Common Pitfalls

WidgetPage

If you have a subclass of widgets.WidgetPage , make sure your template accesses the widget it's displaying with the 'self.widget' object. For example, if you want to get the title from the current widget you're displaying:

template = '''\
<html>
<head><title>%%%%self.widget.title%%%%</title>
</head></html>
'''

instead of:

template = '''\
<html>
<head><title>%%%%title%%%%</title></head>
</html>
'''

Adding Widgets to a Gadget

I had some code like this in one of my Gadgets: self.putWidget("Foo", widgets.TitleBox(MyWidget())). Later whenever trying to access this widget I got this traceback (word wrapped for readability):

Traceback evaluating code in twisted.words.webwords.Page:Traceback
(most recent call last): 
  File "/usr/lib/python2.1/site-packages/twisted/web/widgets.py",
  line 86, in display 
    x = eval(elem, namespace, namespace) 
  File "<string>", line 0, in ? 
AttributeError: TitleBox instance has no attribute 'getHeader' 

Now remember, widgets that you add to a gadget with putWidget are rendered with self.pageFactory like so: self.pageFactory(theChildWidget). The problem is, theChildWidget in this case was actually TitleBox! and of course, TitleBox doesn't follow our template's protocol of having a 'getHeader' method. So, the lesson is: do not wrap your real widgets with other widgets when adding to a Gadget: do formatting either in a) the template or b) the widget's display() method.

Return values of display()

If you ever get this traceback (word wrapped for readability):

web.Server Traceback 
 
Traceback (most recent call last): 
  File "/home/punck/cvs/Twisted/twisted/web/server.py", line 215, in process 
    body = resrc.render(self) 
  File "/usr/lib/python2.1/site-packages/twisted/web/widgets.py", line 408,
  in render 
    displayed = self.display(request) 
  File "/usr/lib/python2.1/site-packages/twisted/web/widgets.py", line 97,
  in display 
    tm.extend(val) 
AttributeError: TitleBox instance has no attribute '__len__' 

It's because you tried to put a widget in the list that display() returns! For now, just tack on .display(request) to all the widgets you want to return in that list.