Model In Depth

  1. Main Concepts
  2. Submodel Paths
  3. The Model Stack and Relative Submodel Paths
  4. IModel Adapters
  5. Registering an IModel adapter for a class
  6. Model Factories
Note:

This HOWTO documents the Model 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.

Model objects provide data to View objects as a Page is being rendered.

Main Concepts

Submodel Paths

Each Model Woven has access to in the tree has a submodel path. Submodel paths start at the Root Model and specify each segment Woven must follow to locate the Model. Submodel paths are slash-separated strings similar to filesystem paths. For the basic Python container types, Dictionaries and Lists, a submodel path segment is simply the key into the container. Given the model:

model = {'name': "Donovan",
         'interests': ["Computers", "Music"],
         'inventory': {'dresser': ['socks','shirts'],
                       'studio': ['Audiowerk8', '800mhz PC', 'iMac'],
                      }
         }

The following submodel paths are valid:

etc...

When woven encouters a model= directive on a node, it will look up the model and pass it to the View object that will render the node:

<html>
    <body>
        <h3 model="/interests/3" view="Text" />
    </body>
</html>

The Model Stack and Relative Submodel Paths

While absolute model paths are useful for specifying exactly which Model data you want associated with a node, the more frequent use case is to specify a relative model path which is a path relative to the Model currently on top of the Model stack. Relative model paths are easy to distinguish because they do not begin with a slash.

When Woven encounters a node with a model= attribute, it looks up the Model object and places it on top of the Model stack. During the processing of this node and all of the node's child nodes, this Model object remains on the top of the stack. Once all child nodes have completed processing, it is popped off of the Model stack.

This means that child nodes can refer to elements of the Model on top of the Model stack with relative submodel paths. For example, we may wish to render the interests list from the above example as two separate HTML elements. To do so, we first place the interests list on top of the Model stack, and then refer to elements of this list:

<html>
  I am interested in:
  <div model="interests" view="None">
    <p>First thing: <span model="0" view="Text" /></p>
    <p>Second thing: <span model="1" view="Text" /></p>
  </div>
</html>

In this case, the interests list was in scope for the duration of the <div> tag, and the individual interest strings were in scope for the duration of the individual <span> tags.

The List widget uses this Stack concept to operate on DOM nodes abstractly, without knowing or caring what directives will occur when the child nodes it returns are handled. We can also use the familiar . and .. concepts from unix shell syntax to refer to Models:

<html>
  <div model="interests" view="List">
    <h3 pattern="listHeader" model="../name" view="Text" />>
    <p pattern="listItem" view="Text" />
    <h6 pattern="listFooter" model="." view="Text" />
  </div>
<html>

The List widget makes copies of the pattern nodes without knowing or caring which directives have been placed on them, or how many children are contained within the node. It then simply sets the model= attribute of each of the nodes to the correct index into the list. More about pattern directives is available in the Views section.

In the above example, even though the interests list had been placed on the Model stack, we were able to access the name string without knowing its absolute path by using the relative path ../name, and we were able to render the interests list with a different View Widget using the relative path ..

The output from generating the above HTML will look like this:

<html>
  <div>
    <h3>Donovan</h3>
    <p>Computers</p>
    <p>Music</p>
    <h6>['Computers', 'Music']</h6>
    </div>
</html>

IModel Adapters

The IModel interface is documented in twisted.web.woven.interfaces.IModel. It describes the interfaces Models must implement in order to participate in the Woven Model stack. If you are inheriting from twisted.web.woven.model.Model, most of these interfaces will be implemented for you.

The interfaces that we will be most interested in implementing are those that are designed to be overridden for customization, getData and setData.

For example, we may wish to create a wrapper for some data which we will retrieve out of a SQL database. To do so, we create a subclass of Model:

class DatabaseJunk(model.Model):
    def getData(self, request):
        someCursor.execute("select * from foo")
        return someCursor.fetchall()
    
    def getSubmodel(self, request, name):
        row = self.getData(request)[int(name)]
        return RowUpdater(row)
  
class RowUpdater(model.Model):
    def __init__(self, id):
        self.row = row
   
    def getData(self, request):
        return self.row
    
    def setData(self, request, data):
        someCursor.execute(
        "update foo set bar=%s, baz=%s where id = %s", 
        (data[0], data[1], self.row[0]))

The result of getData must be an IModel implementor, or may be a Deferred. Thus you may use the IModel interface to produce data from an adbapi call, a pb call, etc. When the data returned is a Deferred, Woven will pause rendering of the current node until the data is available.

Registering an IModel adapter for a class

Woven makes use of the twisted component system. Components, which are discussed in the Components section, allow classes to declare that they implement a specific Interface for another class. This is useful if you already have classes in which you store data, and wish to create thin IModel adapter wrappers around them:

class MyData:
    def __init__(self, something=""):
        self.something = something
    
class MyDataModel(models.MethodModel):
    ## When the MyDataModel adapter is wrapped around an instance
    ## of MyData, the original MyData instance will be stored in 'orig'
    def wmfactory_something(self, request):
        return self.orig.something
    
from twisted.python import components
from twisted.web.woven import interfaces
    
components.registerAdapter(MyDataModel, MyData, interfaces.IModel)

Model Factories

Using a separate Model class for each individual piece of data in the system makes sense when you are able to generalize your Model classes enough so they are reusable. However, it is often easier, especially if you need to perform highly varied SQL calls to produce your data, to use a Model which supports Model factories.

There are two ways to use Model factories. The first is to have a separate Model class which subclasses model.MethodModel. The second is to simply not pass any Model at all to the Page instance, in which case the Page itself will act as a MethodModel.

MethodModel classes should provide methods prefixed with wmfactory_, which will be called when the directive model= is present in a template. For example, given the node <div model="foo" />, a method named wmfactory_foo will be called:

class MyModel(model.MethodModel):
    def wmfactory_foo(self, request):
        return ['foo', 'bar', 'baz']

If you did not pass any Model object when you created your Page instance, the Page class will act as a MethodModel. Thus, you can place your wmfactory_ methods directly on your Page subclass:

class MyPage(page.Page):
    def wmfactory_foo(self, request):
        return ['foo', 'bar', 'baz']

Model factories are a useful way to write some Python code which generates your page model data, for pages which need to look up or calculate data in some way.

Index

Version: 1.3.0