<?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" lang="en"><head><title>Twisted Documentation: PicturePile: a tutorial Woven application</title><link href="../howto/stylesheet.css" type="text/css" rel="stylesheet" /></head><body bgcolor="white"><h1 class="title">PicturePile: a tutorial Woven application</h1><div class="toc"><ol><li><a href="#auto0">Custom Views</a></li><li><a href="#auto1">Simple Input Handling</a></li><li><a href="#auto2">Sessions</a></li><ul><li><a href="#auto3">Controllers</a></li></ul></ol></div><div class="content"><span></span><div class="note"><strong>Note: </strong><p> The PicturePile tutorial illustrates a simple Woven web application. However, 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 tutorial is maintained only for users with an existing Woven codebase. </p></div><p>To illustrate the basic design of a Woven app, we're going to walk through building a simple image gallery. Given a directory of images, it will display a listing of that directory; when a subdirectory or image is clicked on, it will be displayed.</p><p>To begin, we write an HTML template for the directory index, and save it as directory-listing.html:</p><pre> <html> <head> <title model="title" view="Text">Directory listing</title> </head> <body> <h1 model="title" view="Text"></h1> <ul model="directory" view="List"> <li pattern="listItem"><a view="Anchor" /></li> <li pattern="emptyList">This directory is empty.</li> </ul> </body> </html> </pre><p>The main things that distinguish a Woven template from standard XHTML are the <code>model</code>, <code>view</code>, and <code>pattern</code> attributes on tags. Predictably, <code>model</code> and <code>view</code> specify which model and view will be chosen to fill the corresponding node. The <code>pattern</code> attribute is used with views that have multiple parts, such as List. This example uses two patterns <code base="twisted.web.woven.widgets">List</code> provides; <code>listItem</code> marks the node that will be used as the template for each item in the list, and <code>emptyList</code> marks the node displayed when the list has no items.</p><p>Next, we create a <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.page.Page.html" title="twisted.web.woven.page.Page">Page</a></code> that will display the directory listing, filling the template above (after a few imports):</p><pre class="python"> <span class="py-src-keyword">import</span> <span class="py-src-variable">os</span><span class="py-src-newline"> </span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">application</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">service</span><span class="py-src-op">,</span> <span class="py-src-variable">internet</span><span class="py-src-newline"> </span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span><span class="py-src-op">.</span><span class="py-src-variable">woven</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">page</span><span class="py-src-newline"> </span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">server</span><span class="py-src-newline"> </span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">microdom</span><span class="py-src-newline"> </span><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span><span class="py-src-keyword">class</span> <span class="py-src-identifier">DirectoryListing</span><span class="py-src-op">(</span><span class="py-src-parameter">page</span><span class="py-src-op">.</span><span class="py-src-parameter">Page</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span><span class="py-src-indent"> </span><span class="py-src-variable">templateFile</span> <span class="py-src-op">=</span> <span class="py-src-string">"directory-listing.xhtml"</span><span class="py-src-newline"> </span> <span class="py-src-variable">templateDirectory</span> <span class="py-src-op">=</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">split</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">abspath</span><span class="py-src-op">(</span><span class="py-src-variable">__file__</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-keyword">def</span> <span class="py-src-identifier">initialize</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-op">*</span><span class="py-src-parameter">args</span><span class="py-src-op">,</span> <span class="py-src-op">**</span><span class="py-src-parameter">kwargs</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span> <span class="py-src-op">=</span> <span class="py-src-variable">kwargs</span><span class="py-src-op">[</span><span class="py-src-string">'directory'</span><span class="py-src-op">]</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wmfactory_title</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-string">"""Model factory for the title. This method will be called to create the model to use when 'model="title"' is found in the template. """</span> <span class="py-src-newline"> </span> <span class="py-src-keyword">return</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wmfactory_directory</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-string">"""Model factory for the directory. This method will be called to create the model to use when 'model="directory"' is found in the template. """</span> <span class="py-src-newline"> </span> <span class="py-src-variable">files</span> <span class="py-src-op">=</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">listdir</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-keyword">for</span> <span class="py-src-variable">i</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">xrange</span><span class="py-src-op">(</span><span class="py-src-variable">len</span><span class="py-src-op">(</span><span class="py-src-variable">files</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">files</span><span class="py-src-op">[</span><span class="py-src-variable">i</span><span class="py-src-op">]</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">files</span><span class="py-src-op">[</span><span class="py-src-variable">i</span><span class="py-src-op">]</span> <span class="py-src-op">=</span> <span class="py-src-variable">files</span><span class="py-src-op">[</span><span class="py-src-variable">i</span><span class="py-src-op">]</span> <span class="py-src-op">+</span> <span class="py-src-string">'/'</span><span class="py-src-newline"> </span> <span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-keyword">return</span> <span class="py-src-variable">files</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">getDynamicChild</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">name</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span> <span class="py-src-comment"># Protect against malicious URLs like '..' </span><span class="py-src-indent"> </span><span class="py-src-keyword">if</span> <span class="py-src-variable">static</span><span class="py-src-op">.</span><span class="py-src-variable">isDangerous</span><span class="py-src-op">(</span><span class="py-src-variable">name</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">return</span> <span class="py-src-variable">static</span><span class="py-src-op">.</span><span class="py-src-variable">dangerousPathError</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-comment"># Return a DirectoryListing or an ImageDisplay resource, depending on </span> <span class="py-src-comment"># whether the path corresponds to a directory or to a file </span> <span class="py-src-dedent"></span><span class="py-src-variable">path</span> <span class="py-src-op">=</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">name</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">exists</span><span class="py-src-op">(</span><span class="py-src-variable">path</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">path</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">return</span> <span class="py-src-variable">DirectoryListing</span><span class="py-src-op">(</span><span class="py-src-variable">directory</span><span class="py-src-op">=</span><span class="py-src-variable">path</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">return</span> <span class="py-src-variable">ImageDisplay</span><span class="py-src-op">(</span><span class="py-src-variable">image</span><span class="py-src-op">=</span><span class="py-src-variable">path</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Due to the somewhat complex inheritance hierarchy in Woven's internals, a lot of processing is done in the <code>__init__</code> method for <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.page.Page.html" title="twisted.web.woven.page.Page">Page</a></code>. Therefore, a separate <code>initialize</code> method is provided so that one can easily access keyword args without having to disturb the internal setup; it is called with the same args that <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.page.Page.__init__.html" title="twisted.web.woven.page.Page.__init__">Page.__init__</a></code> receives.</p><p>The <code>templateFile</code> attribute tells the Page what file to load the template from; in this case, we will store the templates in the same directory as the Python module. The <code>wmfactory</code> (short for Woven Model Factory) methods return objects to be used as models; In this case, <code>wmfactory_title</code> will return a string, the directory's name, and <code>wmfactory_directory</code> will return a list of strings, the directory's content.</p><p>Upon rendering, Woven will scan the template's DOM tree for nodes to fill; when it encounters one, it gets the model (in this case by calling methods on the Page prefixed with <code>wmfactory_</code>), then creates a view for that model; this page uses standard widgets for its models and so contains no custom view code. The view fills the DOM node with the appropriate data. Here, the view for <code>title</code> is <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.widgets.Text.html" title="twisted.web.woven.widgets.Text">Text</a></code>, and so will merely insert the string. The view for <code>directory</code> is <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.widgets.List.html" title="twisted.web.woven.widgets.List">List</a></code>, and so each element of the list will be formatted within the '<ul>'. Since the view for list items is Anchor, each item in the list will be formatted as an <code><a></code> tag.</p><p>So, for a directory <q>Images</q> containing <q>foo.jpeg</q>, <q>baz.png</q>, and a directory <q>MoreImages</q>, the rendered page will look like this:</p><pre> <html> <head> <title>/Users/ashort/Pictures</title> </head> <body> <h1>/Users/ashort/Pictures</h1> <ul> <li> <a href="foo.jpeg">foo.jpeg</a> </li> <li> <a href="baz.png">baz.png</a> </li> <li> <a href="MoreImages/">MoreImages/</a> </li> </ul> </body> </html> </pre><p>As you can see, the nodes marked with <code>model</code> and <code>view</code> are replaced with the data from their models, as formatted by their views. In particular, the List view repeated the node marked with the <code>listItem</code> pattern for each item in the list.</p><p>For displaying the actual images, we use this template, which we save as image-display.html:</p><pre> <html> <head> <title model="image" view="Text">Filename</title> </head> <body> <img src="preview" /> </body> </html> </pre> And here is the definition of <code>ImageDisplay</code>: <pre class="python"> <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span><span class="py-src-op">.</span><span class="py-src-variable">web</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">static</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span><span class="py-src-keyword">class</span> <span class="py-src-identifier">ImageDisplay</span><span class="py-src-op">(</span><span class="py-src-parameter">page</span><span class="py-src-op">.</span><span class="py-src-parameter">Page</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span><span class="py-src-indent"> </span><span class="py-src-variable">templateFile</span><span class="py-src-op">=</span><span class="py-src-string">"image-display.xhtml"</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-keyword">def</span> <span class="py-src-identifier">initialize</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-op">*</span><span class="py-src-parameter">args</span><span class="py-src-op">,</span> <span class="py-src-op">**</span><span class="py-src-parameter">kwargs</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">image</span> <span class="py-src-op">=</span> <span class="py-src-variable">kwargs</span><span class="py-src-op">[</span><span class="py-src-string">'image'</span><span class="py-src-op">]</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wmfactory_image</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">return</span> <span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">image</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wchild_preview</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">return</span> <span class="py-src-variable">static</span><span class="py-src-op">.</span><span class="py-src-variable">File</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">image</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Instead of using <code>getDynamicChild</code>, this class uses a <code>wchild_</code> method to return the image data when the <code>preview</code> child is requested. <code>getDynamicChild</code> is only called if there are no <code>wchild_</code> methods available to handle the requested URL.</p><p>Finally, we create a webserver set to start with a directory listing, and connect it to a port. We will tell this Site to serve a DirectoryListing of a directory named <q>Pictures</q> in our home directory:</p><pre class="python"> <span class="py-src-variable">rootDirectory</span> <span class="py-src-op">=</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">expanduser</span><span class="py-src-op">(</span><span class="py-src-string">"~/Pictures"</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-variable">site</span> <span class="py-src-op">=</span> <span class="py-src-variable">server</span><span class="py-src-op">.</span><span class="py-src-variable">Site</span><span class="py-src-op">(</span><span class="py-src-variable">DirectoryListing</span><span class="py-src-op">(</span><span class="py-src-variable">directory</span><span class="py-src-op">=</span><span class="py-src-variable">rootDirectory</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-variable">application</span> <span class="py-src-op">=</span> <span class="py-src-variable">service</span><span class="py-src-op">.</span><span class="py-src-variable">Application</span><span class="py-src-op">(</span><span class="py-src-string">"ImagePool"</span><span class="py-src-op">)</span> <span class="py-src-newline"> </span><span class="py-src-variable">parent</span> <span class="py-src-op">=</span> <span class="py-src-variable">service</span><span class="py-src-op">.</span><span class="py-src-variable">IServiceCollection</span><span class="py-src-op">(</span><span class="py-src-variable">application</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-variable">internet</span><span class="py-src-op">.</span><span class="py-src-variable">TCPServer</span><span class="py-src-op">(</span><span class="py-src-number">8088</span><span class="py-src-op">,</span> <span class="py-src-variable">site</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">setServiceParent</span><span class="py-src-op">(</span><span class="py-src-variable">parent</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-endmarker"></span></pre><p>And then start the server by running the following command-line: <code class="shell">twistd -noy picturepile.py</code>.</p><h2>Custom Views<a name="auto0"></a></h2><p>Now, let's add thumbnails to our directory listing. We begin by changing the view for the links to <q>thumbnail</q>:</p><pre> <html> <head> <title model="title" view="Text">Directory listing</title> </head> <body> <h1 model="title" view="Text"></h1> <ul model="directory" view="List"> <li pattern="listItem"><a view="thumbnail" /></li> <li pattern="emptyList">This directory is empty.</li> </ul> </body> </html> </pre><p>Woven doesn't include a standard <q>thumbnail</q> widget, so we'll have to write the code for this view ourselves. (Standard widgets are named with initial capital letters; by convention, custom views are named like methods, with initial lowercase letters.)</p><p>The simplest way to do it is with a <code>wvupdate_</code> (short for Woven View Update) method on our DirectoryListing class:</p><pre class="python"> <span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_thumbnail</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">node</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">a</span> <span class="py-src-op">=</span> <span class="py-src-variable">microdom</span><span class="py-src-op">.</span><span class="py-src-variable">lmx</span><span class="py-src-op">(</span><span class="py-src-variable">node</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-variable">a</span><span class="py-src-op">[</span><span class="py-src-string">'href'</span><span class="py-src-op">]</span> <span class="py-src-op">=</span> <span class="py-src-variable">data</span><span class="py-src-newline"> </span> <span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">img</span><span class="py-src-op">(</span><span class="py-src-variable">src</span><span class="py-src-op">=</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">+</span><span class="py-src-string">'/preview'</span><span class="py-src-op">)</span><span class="py-src-op">,</span><span class="py-src-variable">width</span><span class="py-src-op">=</span><span class="py-src-string">'200'</span><span class="py-src-op">,</span><span class="py-src-variable">height</span><span class="py-src-op">=</span><span class="py-src-string">'200'</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>When the <code>thumbnail</code> view is requested, this method is called with the HTTP request, the DOM node marked with this view, and the data from the associated model (in this case, the name of the image or directory). With this approach, we can now modify the DOM as necessary. First, we wrap the node in <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.microdom.lmx.html" title="twisted.web.microdom.lmx">lmx</a></code>, a class provided by Twisted's DOM implementation that provides convenient syntax for modifying DOM nodes; attributes can be treated as dictionary keys, and the <code>text</code> and <code>add</code> methods provide for adding text to the node and adding children, respectively. If this item is a directory, a textual link is displayed; else, it produces an <code>IMG</code> tag of fixed size.</p><h2>Simple Input Handling<a name="auto1"></a></h2><p>Limiting thumbnails to a single size is rather inflexible; our app would be nicer if one could adjust it. Let's add a list of thumbnail sizes to the directory listing. Again, we start with the template:</p><div class="html-listing"><pre class="htmlsource"> <html> <head> <title model="title" view="Text">Directory listing</title> </head> <body> <h1 model="title" view="Text"></h1> <form action=""> Thumbnail size: <select name="thumbnailSize" onChange="submit()" view="adjuster"> <option value="400">400x400</option> <option value="200">200x200</option> <option value="100">100x100</option> <option value="50">50x50</option> </select> </form> <ul model="directory" view="List"> <li pattern="listItem"><a view="thumbnail" /></li> <li pattern="emptyList">This directory is empty.</li> </ul> </body> </html> </pre><div class="caption">Source listing - <a href="listings/PicturePile/directory-listing3.html"><span class="filename">listings/PicturePile/directory-listing3.html</span></a></div></div><p>This time, we add a form with a list of thumbnail sizes named <code>thumbnailSize</code>: we want the form to reflect the selected option, so we place an <code>adjuster</code> view on the <code>select</code> tag that looks for the right <code>option</code> tag and puts <code>selected=1</code> on it (the default size being 200):</p><pre class="python"> <span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_adjuster</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">widget</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">args</span><span class="py-src-op">.</span><span class="py-src-variable">get</span><span class="py-src-op">(</span><span class="py-src-string">'thumbnailSize'</span><span class="py-src-op">,</span><span class="py-src-op">(</span><span class="py-src-string">'200'</span><span class="py-src-op">,</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-newline"> </span> <span class="py-src-variable">domhelpers</span><span class="py-src-op">.</span><span class="py-src-variable">locateNodes</span><span class="py-src-op">(</span><span class="py-src-variable">widget</span><span class="py-src-op">.</span><span class="py-src-variable">node</span><span class="py-src-op">.</span><span class="py-src-variable">childNodes</span><span class="py-src-op">,</span> <span class="py-src-nl"> </span> <span class="py-src-string">'value'</span><span class="py-src-op">,</span> <span class="py-src-variable">size</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-op">.</span><span class="py-src-variable">setAttribute</span><span class="py-src-op">(</span><span class="py-src-string">'selected'</span><span class="py-src-op">,</span> <span class="py-src-string">'1'</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p><code>request.args</code> is a dictionary, mapping argument names to lists of values (since multiple HTTP arguments are possible). In this case, we only care about the first argument named <code>thumbnailSize</code>. <code>domhelpers.locateNodes</code> is a helper function which, given a list of DOM nodes, a key, and a value, will search each tree and return all nodes that have the requested key-value pair.</p><p>Next, we modify the <code>thumbnail</code> view to look at the arguments from the HTTP request and use that as the size for the images:</p><pre class="python"> <span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_thumbnail</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">node</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">args</span><span class="py-src-op">.</span><span class="py-src-variable">get</span><span class="py-src-op">(</span><span class="py-src-string">'thumbnailSize'</span><span class="py-src-op">,</span><span class="py-src-op">(</span><span class="py-src-string">'200'</span><span class="py-src-op">,</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-newline"> </span> <span class="py-src-variable">a</span> <span class="py-src-op">=</span> <span class="py-src-variable">microdom</span><span class="py-src-op">.</span><span class="py-src-variable">lmx</span><span class="py-src-op">(</span><span class="py-src-variable">node</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-variable">a</span><span class="py-src-op">[</span><span class="py-src-string">'href'</span><span class="py-src-op">]</span> <span class="py-src-op">=</span> <span class="py-src-variable">data</span><span class="py-src-newline"> </span> <span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">img</span><span class="py-src-op">(</span><span class="py-src-variable">src</span><span class="py-src-op">=</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">+</span><span class="py-src-string">'/preview'</span><span class="py-src-op">)</span><span class="py-src-op">,</span><span class="py-src-variable">width</span><span class="py-src-op">=</span><span class="py-src-variable">size</span><span class="py-src-op">,</span><span class="py-src-variable">height</span><span class="py-src-op">=</span><span class="py-src-variable">size</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><h2>Sessions<a name="auto2"></a></h2><p>A disadvantage to the approach taken in the previous section is that subdirectories do receive the same thumbnail sizing as their parents; also, reloading the page sets it back to the default size of 200x200. To remedy this, we need a way to store data that lasts longer than a single page render. Fortunately, twisted.web provides this in the form of a Session object. Since only one Session exists per user for all applications on the server, the Session object is Componentized, and each application adds adapters to contain their own state and behaviour, as explained in the <a href="components.html">Components</a> documentation. So, we start with an interface, and a class that implements it, and registration of our class upon Session:</p><pre class="python"> <span class="py-src-keyword">class</span> <span class="py-src-identifier">IPreferences</span><span class="py-src-op">(</span><span class="py-src-parameter">components</span><span class="py-src-op">.</span><span class="py-src-parameter">Interface</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">pass</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span><span class="py-src-dedent"></span><span class="py-src-keyword">class</span> <span class="py-src-identifier">Preferences</span><span class="py-src-op">(</span><span class="py-src-parameter">components</span><span class="py-src-op">.</span><span class="py-src-parameter">Adapter</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">__implements__</span> <span class="py-src-op">=</span> <span class="py-src-variable">IPreferences</span><span class="py-src-newline"> </span> <span class="py-src-nl"> </span><span class="py-src-dedent"></span><span class="py-src-variable">components</span><span class="py-src-op">.</span><span class="py-src-variable">registerAdapter</span><span class="py-src-op">(</span><span class="py-src-variable">Preferences</span><span class="py-src-op">,</span> <span class="py-src-variable">server</span><span class="py-src-op">.</span><span class="py-src-variable">Session</span><span class="py-src-op">,</span> <span class="py-src-variable">IPreferences</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-endmarker"></span></pre><p>We're just going to store data on this class, so no methods are defined.</p><p>Next, we change our view methods, <code>wvupdate_thumbnail</code> and <code>wvupdate_adjuster</code>, to retrieve their size data from the Preferences object stored on the Session, instead of the HTTP request:</p><pre class="python"> <span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_thumbnail</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">node</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">prefs</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">getSession</span><span class="py-src-op">(</span><span class="py-src-variable">IPreferences</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">getattr</span><span class="py-src-op">(</span><span class="py-src-variable">prefs</span><span class="py-src-op">,</span> <span class="py-src-string">'size'</span><span class="py-src-op">,</span><span class="py-src-string">'200'</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-variable">a</span> <span class="py-src-op">=</span> <span class="py-src-variable">microdom</span><span class="py-src-op">.</span><span class="py-src-variable">lmx</span><span class="py-src-op">(</span><span class="py-src-variable">node</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-variable">a</span><span class="py-src-op">[</span><span class="py-src-string">'href'</span><span class="py-src-op">]</span> <span class="py-src-op">=</span> <span class="py-src-variable">data</span><span class="py-src-newline"> </span> <span class="py-src-keyword">if</span> <span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">isdir</span><span class="py-src-op">(</span><span class="py-src-variable">os</span><span class="py-src-op">.</span><span class="py-src-variable">path</span><span class="py-src-op">.</span><span class="py-src-variable">join</span><span class="py-src-op">(</span><span class="py-src-variable">self</span><span class="py-src-op">.</span><span class="py-src-variable">directory</span><span class="py-src-op">,</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-dedent"></span><span class="py-src-keyword">else</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">a</span><span class="py-src-op">.</span><span class="py-src-variable">img</span><span class="py-src-op">(</span><span class="py-src-variable">src</span><span class="py-src-op">=</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">+</span><span class="py-src-string">'/preview'</span><span class="py-src-op">)</span><span class="py-src-op">,</span><span class="py-src-variable">width</span><span class="py-src-op">=</span><span class="py-src-variable">size</span><span class="py-src-op">,</span><span class="py-src-variable">height</span><span class="py-src-op">=</span><span class="py-src-variable">size</span><span class="py-src-op">)</span><span class="py-src-op">.</span><span class="py-src-variable">text</span><span class="py-src-op">(</span><span class="py-src-variable">data</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-nl"> </span> <span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-keyword">def</span> <span class="py-src-identifier">wvupdate_adjuster</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">widget</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">prefs</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">getSession</span><span class="py-src-op">(</span><span class="py-src-variable">IPreferences</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">getattr</span><span class="py-src-op">(</span><span class="py-src-variable">prefs</span><span class="py-src-op">,</span> <span class="py-src-string">'size'</span><span class="py-src-op">,</span><span class="py-src-string">'200'</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-variable">domhelpers</span><span class="py-src-op">.</span><span class="py-src-variable">locateNodes</span><span class="py-src-op">(</span><span class="py-src-variable">widget</span><span class="py-src-op">.</span><span class="py-src-variable">node</span><span class="py-src-op">.</span><span class="py-src-variable">childNodes</span><span class="py-src-op">,</span> <span class="py-src-nl"> </span> <span class="py-src-string">'value'</span><span class="py-src-op">,</span> <span class="py-src-variable">size</span><span class="py-src-op">)</span><span class="py-src-op">[</span><span class="py-src-number">0</span><span class="py-src-op">]</span><span class="py-src-op">.</span><span class="py-src-variable">setAttribute</span><span class="py-src-op">(</span><span class="py-src-string">'selected'</span><span class="py-src-op">,</span> <span class="py-src-string">'1'</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><h3>Controllers<a name="auto3"></a></h3><p>Now we turn to the question of how the data gets into the session in the first place. While it is possible to to place it there from within the <code>wvupdate_</code> methods, since they both have access to the HTTP request, it is desirable at times to separate out input handling, which is what controllers are for. So, we add a <code>wcfactory_</code> (short for Woven Controller Factory) method to DirectoryListing:</p><pre class="python"> <span class="py-src-keyword">def</span> <span class="py-src-identifier">wcfactory_adjuster</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">node</span><span class="py-src-op">,</span> <span class="py-src-parameter">model</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">return</span> <span class="py-src-variable">ImageSizer</span><span class="py-src-op">(</span><span class="py-src-variable">model</span><span class="py-src-op">,</span> <span class="py-src-variable">name</span><span class="py-src-op">=</span><span class="py-src-string">'thumbnailSize'</span><span class="py-src-op">)</span><span class="py-src-newline"> </span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>ImageSizer is a controller. It checks the input for validity (in this case, since it subclasses <code class="API"><a href="http://twistedmatrix.com/documents/TwistedDocs/TwistedDocs-1.3.0/api/twisted.web.woven.input.Anything.html" title="twisted.web.woven.input.Anything">Anything</a></code>, it merely ensures the input is non-empty) and calls <code>handleValid</code> if the check succeeds; in this case, we retrieve the Preferences component from the session, and store the size received from the form upon it:</p><pre class="python"> <span class="py-src-keyword">class</span> <span class="py-src-identifier">ImageSizer</span><span class="py-src-op">(</span><span class="py-src-parameter">input</span><span class="py-src-op">.</span><span class="py-src-parameter">Anything</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-keyword">def</span> <span class="py-src-identifier">handleValid</span><span class="py-src-op">(</span><span class="py-src-parameter">self</span><span class="py-src-op">,</span> <span class="py-src-parameter">request</span><span class="py-src-op">,</span> <span class="py-src-parameter">data</span><span class="py-src-op">)</span><span class="py-src-op">:</span><span class="py-src-newline"> </span><span class="py-src-indent"> </span><span class="py-src-variable">prefs</span> <span class="py-src-op">=</span> <span class="py-src-variable">request</span><span class="py-src-op">.</span><span class="py-src-variable">getSession</span><span class="py-src-op">(</span><span class="py-src-variable">IPreferences</span><span class="py-src-op">)</span><span class="py-src-newline"> </span> <span class="py-src-variable">prefs</span><span class="py-src-op">.</span><span class="py-src-variable">size</span> <span class="py-src-op">=</span> <span class="py-src-variable">data</span><span class="py-src-newline"> </span><span class="py-src-dedent"></span><span class="py-src-dedent"></span><span class="py-src-endmarker"></span></pre><p>Finally, we must modify the template to use our new controller. Since we are concerned with the input from the <code><select></code> element of the form, we place the controller upon it:</p><div class="html-listing"><pre class="htmlsource"> <html> <head> <title model="title" view="Text">Directory listing</title> </head> <body> <h1 model="title" view="Text"></h1> <form action=""> Thumbnail size: <select name="thumbnailSize" onChange="submit()" view="adjuster" controller="adjuster"> <option value="400">400x400</option> <option value="200">200x200</option> <option value="100">100x100</option> <option value="50">50x50</option> </select> </form> <ul model="directory" view="List"> <li pattern="listItem"><a view="thumbnail" /></li> <li pattern="emptyList">This directory is empty.</li> </ul> </body> </html> </pre><div class="caption">Source listing - <a href="listings/PicturePile/directory-listing4.html"><span class="filename">listings/PicturePile/directory-listing4.html</span></a></div></div><p>Now, the selected size will be remembered across subdirectories and page reloads.</p></div><p><a href="../howto/index.html">Index</a></p><span class="version">Version: 1.3.0</span></body></html>