woven.xhtml   [plain text]


<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Woven</title></head><body>

<div class="note">
<p>
The Woven framework should not be used for new projects. The newer <a href="http://www.divmod.org/Home/Projects/Nevow/">Nevow</a> framework, available as part of the <a href="http://www.divmod.org/Quotient/">Quotient</a> project, is a simpler framework with consistent semantics and better testing and is strongly recommended over Woven.
</p>

<p>
The Woven documentation below is maintained only for users with an existing Woven codebase.
</p>
</div>
<h1>Woven</h1>


<h2>Introduction</h2>

<p>Woven is a Web Application Framework for building highly interactive web applications, written in Python. It separates HTML Templates from page-generation logic written in Python, and uses the Model View Controller (MVC) pattern to create dynamic HTML on the fly. Woven is higher level framework that depends on the Twisted Web package of the Twisted Framework.</p>

<h2>Twisted Overview</h2>

  <p>Twisted is a framework written in Python for building network applications. A core design feature of Twisted is its asynchronous networking nature. Because of the high overhead and locking requirements of threads, Twisted chooses to instead use the highly efficient network event notification mechanisms provided by the OS in the form of the C <code>poll()</code> or <code>select()</code> calls. Twisted uses the Reactor pattern, which is an event-loop style of programming which facilitates asynchronous programming.</p>

<p>Asynchronous programming requires breaking your program into smaller function chunks that trigger an operation which may potentially take a long time (for example, a network request) and return. In order to continue the flow of your code when the operation has completed, you must register a <q>callback function</q> which will be called with the resulting data when it is ready. Twisted includes a generalized callback handling mechanism, Deferred, discussed in <a href="http://www.twistedmatrix.com/documents/historic/2003/pycon/deferex/deferex.html">the Deferred execution paper</a>.</p>

<p>However, since writing Woven applications already involves breaking your functions into small, reusable Model-View-Controller components, Woven is able to handle the asynchronous nature of Twisted for you. By writing Models which provide data, potentially asynchronously, and Views which render data when it is ready, you are doing all that is required to act as a good citizen within the Twisted framework.</p>

<h2>Twisted Web Object Publishing and Woven</h2>

  <p>Twisted includes a Web Server that handles HTTP requests and returns dynamic pages. It is useful when used in conjunction with Apache for serving static files such as Images. Apache can be set up to forward a specific URL via <code>ProxyPass</code> to the Twisted Web server. Twisted Web uses the concept of Object Publishing, similar to Zope, where there is a root Python Object below which all other Objects representing URLs are located.</p>

<p>When a request comes in to Twisted Web, Twisted Web splits the URL into
segments and begins looking for Objects below the root by calling getChild
tail-recursively. For example, if the URL
<code>http://example.com/foo/bar/baz</code> is requested, Twisted splits this
into the list of path segments <code class="python">['foo', 'bar',
'baz']</code>. It then calls <code class="python">root.getChild('foo')</code>, calls <code class="python">getChild('bar')</code> on the result, calls <code class="python">getChild('baz')</code> on the second result, and finally calls <code class="python">render(request)</code> on the result of the final <code class="python">getChild</code> call.</p>

<p>For more details about Twisted Web, see <a href="web-overview.xhtml">Overview
of Twisted Web</a>.</p>

<h2>Smalltalk Model-View-Controller Overview</h2>

  <p>Originally developed for Smalltalk, the MVC design pattern is a flexible mechanism for creating both GUI and web application user interfaces. The primary advantage of the MVC pattern is separation of business logic from presentation details and provides a <em>loose coupling</em> between an application's Model (state) and View (presentation). All of this makes code reuse easier and enables a division of labor between application design and user interface design, albeit at the expense of a little extra work.</p>

<p>A <q>Model</q> is an object that represents or encapsulates an application's business logic or state. The model contains both data and business logic code, but does not contain presentation or rendering code.</p>

<p>A <q>View</q> is an object that contains presentation or rendering code, but does not contain business logic code.</p>

<p>Finally, a <q>Controller</q> is a dispatcher object that mediates flow between the Model and the View. In traditional Smalltalk MVC, the Controller is responsible for polling the Mouse and Keyboard and converting the user's actions (Click a button, for example) into high-level events (Change the Model data from 0 to 1, and redraw a View which represents this Model to the user).</p>

<h2>Woven Model-View-Controller Overview</h2>

  <p>In Woven, a Model is a python object that holds the data that will eventually be rendered by a view object into a portion of an HTML page. Woven Models may be any Python object; Woven accomplishes this using <q><code>IModel</code> Adapters</q>:Components. Since different Python objects may act as <q>Containers</q> using different semantics, <code>IModel</code> adapters are required to allow Woven to address all Container objects uniformly. For example, Dictionaries are indexed using string keys. Lists are indexed using integer keys. Objects provide references to other objects using dot syntax (<code>foo.bar</code>), not square bracket syntax (<code>foo['bar']</code>). <code>IModel</code> Adapters are provided for all the basic Python types, Dictionaries, Lists, Integers, Floats, Strings, and Instances, and work behind the scenes to provide Model data to your Pages.</p>

<p>In Woven, a View is comprised of a Page object and many Widget objects. A Page object loads a template file (an XHTML file) that contains references to Widget objects (python objects that replace specific portions of the DOM created from the XHTML file). A single XHTML template references one Page object, but many Widget objects.</p>

<p>Widgets come in two flavors: local and global. Local Widgets are specific to only one template file, because the logic they perform is very specific, while global Widgets are general enough to replace placeholder content in many template files. Local Widgets are defined on the Page class, by defining a method with the prefix <code>wvupdate_</code>. Global Widgets are defined as subclasses of the Widget class.</p>

<p>In Woven, Page objects act as the default Controller object, similar to Servlets in Struts. ((please explain further?)) Since the Web is Request-Response and not event-driven like a Smalltalk application, the most basic event to be handled from the user is <q>URL Requested</q>. The root Page object contains logic for locating the correct Page object to handle the request. Once this object is located, it handles the <q>url requested</q> event by rendering itself into HTML. Thus, the Page object acts as both the main View and Controller for this event.</p>

<p>However, unlike Struts, Woven also supports more Smalltalk-like Controller programming. With a Controller, it is possible to register a Python event handling function which will be called when a specific JavaScript event occurs in the Browser. These events are relayed to the server out-of-band (without causing the main page to refresh) and can be handled asynchronously. Thus, it is possible to program Web Applications which act more like traditional Smalltalk Desktop Applications.</p>

<p>Often in Woven, it is convenient for a single object to have both the View and Controller responsibilities, though this is not strictly necessary. It is only useful to split out the Controller logic from the View if the request argument handling is general enough to be reusable across multiple pages.</p>

<h2>Overview of Woven Main Concepts</h2>

<ul>
    <li><a href="#templates">XHTML Templates</a> must be valid XHTML documents.
    They provide a skeleton html page with placeholder content. Any element on
    the page which is destined for dynamic content must include special
    attributes to specify the name of model and view, like this: <code>&lt;tag
    model="aModel" view="aView"&gt;placeholder text&lt;/tag&gt;</code></li>
    
    <li><a href="#models">Model</a> objects provide data for display in a web page.</li>
    
    <li><a href="#views">View</a> objects are given a DOM node created from the HTML template and model data, and are responsible for inserting the data into the node using the DOM api.</li>
    
    <li><a href="#controllers">Controller</a> objects accept input from the request and update the Model objects with the new input.</li>

    <li><a href="#pages">Page</a> objects tie a model tree to a template, and provide view and controller factories to the template. This is the entry point of a web page built using Woven.</li>

    <li><a href="#rendering">Rendering</a> occurs when a web browser visits a Page. Woven recurses the template looking for nodes to handle, connects the correct Model object to a View and a Controller, and invokes them to render the node.</li>

    <li><a href="#furtherconcepts">Further Concepts</a> contains links to pages with more in depth information about Woven components.</li>

</ul>

<h2>In Depth Pages about Woven components</h2>

<ul>
  <li><a href="web-overview.xhtml">Twisted Web</a></li>
  <li><a href="modelindepth.xhtml">Model</a></li>
  <li><a href="viewindepth.xhtml">View</a></li>
  <li><a href="controllerindepth.xhtml">Controller</a></li>
  <li><a href="pageindepth.xhtml">Page</a></li>
  <li><a href="livepage.xhtml">LivePage</a></li>
  <li><a href="formindepth.xhtml">Form</a></li>
  <li><a href="guardindepth.xhtml">Guard</a></li>
</ul>

<img title="Overview of the Woven rendering process" src="../img/woven_overview.png" />

<h2>Templates</h2><a name="templates" />

  <p>Templates in Woven are XHTML documents, marked up with four special woven attributes:</p>

<ul>
    
  <li><code>model=</code> indicates which model object will be associated with this node.</li>

  <li><code>view=</code> indicates which view object will be associated with this node.</li>

  <li><code>controller=</code> indicates which controller object will be associated with this node.</li>

  <li><code>pattern=</code> marks a node so it may be located by the View code, without the view knowing where the pattern= node is located or what style the pattern node contains.</li>
</ul>

<h3>HTML Template example</h3>

<pre>
  &lt;html&gt;
    &lt;body&gt;
      &lt;h3 model="name" view="Text" /&gt;
      &lt;div model="stuff" view="List"&gt;
        &lt;span pattern="listItem" view="Text" /&gt;
      &lt;/div&gt;
    &lt;/body&gt;
  &lt;/html&gt;
</pre>

<h2>Models</h2><a name="models" />

  <p>Model objects are arranged in a tree. Each Woven page has a model tree with exactly one root. All data required for display by a Woven page is made available through named sub-models below this root.</p>

  <p>Any Python object may be used as a Woven model.</p>

<p>An example of a model tree built using simple python data structures, Dictionaries, Lists, and Strings:</p>

<pre class="python">
    model = {
      'name': 'Welcome to Woven',
      'stuff': [
        'Foo',
        'Bar',
        'Baz'
      ]
    }
</pre>

<p>Each model in this tree has a submodel path which we can use to refer to it.</p>

<ul>
<li>The main dictionary is named <code>/</code></li>

<li>The name string is named <code>/name</code></li>

<li>The stuff list is named <code>/stuff</code></li>

<li>The first element of the stuff list is named <code>/stuff/0</code></li>

<li>The second element of the stuff list is named <code>/stuff/1</code></li>

<li>The third element of the stuff list is named <code>/stuff/2</code></li>

</ul>

<h2>Views</h2><a name="views" />

  <p>View objects are constructed each time a <code>view=</code> directive is encountered.</p>

  <p>If <code>view="Text"</code> is specified on a <code>Node</code>, an instance of the <code>Text</code> class will be constructed to handle the node.</p>

<p>An example View widget which simply inserts the model data into the DOM as a text node:</p>

<pre class="python">
class Text(view.View):
    def generate(self, request, node, model):
        data = str(model.getData())
        newTextNode = request.d.createTextNode(data)
        node.appendChild(newTextNode)
        return node
</pre>

<p>The node that is returned from the generate method replaces the template node in the final HTML output.</p>

<div class="note">In the above case, the same node that was passed in was returned, after being changed (<q>mutated</q>).</div>

<p>The <code>View</code> object should return a DOM node that has been populated with the given model data.</p>

<h2>Controllers</h2><a name="controllers" />

  <p>Controllers are responsible for accepting input from the user and making appropriate changes to the model.</p>

  <p>They are also responsible for ensuring that the data submitted by the user is valid before changing the model.</p>

  <p>Very few applications need the flexibility of separate Controller objects. Often, is is more convenient and clear to place the Controller logic in the View, where the View can make sure it has the latest data before rendering itself.</p>

<p>An example of a Controller which verifies the user's input before committing it to the Model:</p>

<pre class="python">
class NewName:
    def handle(self, request):
        newName = request.args.get("newname", None)
        if newName is None:
          ## The user did not submit the form; do not do anything.
          return
        if newName == "":
          self.view.setError("Sorry, you didn't enter a name.")
        else:
          self.model.setData(newName)
          ## Tell the model that we are done making changes to it, 
          ## and Views that rely upon this model should rerender.
          self.model.notify({'request': request})
</pre>

<h2>Pages</h2><a name="pages" />

  <p>Pages are the entry point into a Woven application. The Page object accepts a request to render a web page from <code>twisted.web</code> and drives the page rendering process by parsing the template, locating Model objects in the tree, and invoking Controller and View objects.</p>

<p>Page is a subclass of Resource, the <code>twisted.web</code> class representing an individual URL. Resource instances can be hooked up to the twisted.web HTTP server in several ways. The simplest way to hook up a Resource to a web URL is to start a static twisted.web server, which will serve files out of the given directory:</p>

<pre class="shell">
% mktap web --path /Users/dsp/Sites
% twistd -nf web.tap
</pre>

<p>If you visit the URL <code>http://localhost:8080/</code>, you will see the
contents of the directory you specified. To create a URL which will be served by
an instance of <code>Page</code>, create a Python script. In this script,
instantiate a <code>Page</code> instance, passing it a <code>Model</code> and a
<code>Template</code>, and assign it to a variable named
<code>resource</code>:</p>

<pre class="python">
from twisted.web.woven import page

model = {'name': 'Welcome to Woven', 
         'stuff': ['Foo', 'Bar', 'Baz']}
  
template = """&lt;html&gt;
  &lt;body&gt;
      &lt;h3 model="name" view="Text" /&gt;
      &lt;div model="stuff" view="List"&gt;
        &lt;p pattern="listItem" view="Text" /&gt;
      &lt;/div&gt;
    &lt;/body&gt;
  &lt;/html&gt;
"""

resource = page.Page(model, template=template)
</pre>

<p>Name this script <code>test.rpy</code> and place it in the directory served by <code>twisted.web</code>. Then visit the URL <code>http://localhost:8080/test.rpy</code> with your web browser, and you should see a page with the HTML-formatted model data.</p>

<h3>Page rendering process</h3><a name="rendering" />

  <p>When Woven renders a page, it first constructs a DOM (Document Object Model) which represents the template in memory using Python objects.</p>

  <p>Woven then examines the DOM using a pre-order traversal looking for nodes with woven directives (nodes with <code>model=</code>, <code>view=</code>, or <code>controller=</code> attributes).</p>

  <p>When a directive node is encountered, Woven locates/constructs Model, View, and Controller objects to handle this node.</p>

  <p>The Controller is then triggered by calling handle. The controller may take any action required of it to update the Model data, or may do nothing.</p>

  <p>The View is then rendered by calling generate. The View object is passed a DOM node and a reference to the Model. The view then manipulates the DOM node, placing the Model data in it.</p>

  <p>The DOM returned from the View is then traversed, looking for directives to handle.</p>

  <p>When the entire DOM has been traversed and mutated, the DOM is converted to HTML and sent to the browser.</p>

<img title="Example of Woven expanding the template (before processing)" src="../img/woven_before.png" />
<img title="Example of Woven expanding the template (after processing)" src="../img/woven_after.png" />

<h2>Further Reading</h2><a name="furtherconcepts" />

<p><a href="web-overview.xhtml">Twisted Web</a> is the Object-publishing web server woven uses to glue HTTP requests to Page rendering.</p>

<p><a href="pageindepth.xhtml">Page</a> objects are the IResource implementors in the Woven framework. They represent URL segments and drive the Woven template rendering process. They also contain convenient methods for specifying Page trees.</p>

<p><a href="modelindepth.xhtml">Model</a> objects provide data to Woven pages. Woven includes IModel adapters for the basic Python types and makes it easy for you to write your own Model classes and IModel adapters for your already-existing classes.</p>

<p><a href="viewindepth.xhtml">View</a> objects insert Model data into the DOM provided by the Template. They use DOM syntax for HTML generation, and have convenient syntax for locating and copying <code>pattern=</code> nodes abstractly.</p>

<p><a href="controllerindepth.xhtml">Controller</a> objects accept input from the request and update the Model data. Controller objects are optional; often your Page or View instances can contain the controller logic and still make sense. Controllers are for cases which are general enough to warrant validation and commit logic.</p>

<p><a href="livepage.xhtml">LivePage</a> objects allow you to build DHTML behaviors into your Woven pages using pure server-side Python.  It includes code for asynchronously forwarding client-side JavaScript events to the server without refreshing the page, and sending JavaScript to a page from the server after it has already been loaded.</p>

<p><a href="formindepth.xhtml">Form</a> is a woven module that makes it easy to create HTML forms from existing Python methods which take keyword arguments. It also supports basic input validation.</p>

<p><a href="guardindepth.xhtml">Guard</a> is a woven module that allows you to wrap your Page instances with login pages, to prevent unauthorized users from accessing them.</p>

</body></html>